¿Puedo obligar al compilador de TypeScript a usar tipado nominal?
TypeScript utiliza subtipado estructural, por lo que esto en realidad es posible:
// there is a class
class MyClassTest {
foo():void{}
bar():void{}
}
// and a function which accepts instances of that class as a parameter
function someFunction(f:MyClassTest):void{
if (f){
if (!(f instanceof MyClassTest)) {
console.log("why")
}
}
}
// but it's valid to pass objects, which aren't in fact instances
// they just need to "look" the same, be "structural subtypes"
someFunction({foo(){}, bar(){}}) //valid!
Sin embargo, como el proveedor de la implementación de someFunction, me gustaría realmente prohibir el paso de objetos estructuralmente similares, pero realmente solo quiero permitir instancias reales de MyClassTest o sus subtipos. Quiero hacer cumplir la "tipificación nominal" al menos para algunas de mis propias declaraciones de funciones.
¿Es esto posible?
Antecedentes: Considere un caso en el que los objetos pasados a la API deben ser de ese tipo, por ejemplo, porque fueron producidos por una fábrica que establece algún estado interno en este objeto y el objeto realmente tiene una interfaz privada que someFunction espera que esté presente para funcionar correctamente. Sin embargo, no quiero revelar esa interfaz privada (por ejemplo, en un archivo de definición de TypeScript), pero quiero que sea un error de compilador si alguien pasa una implementación falsa. Ejemplo concreto: me gustaría que el compilador de TypeScript se quejara en este caso, incluso si proporciono todos los miembros de la siguiente manera:
//OK for typescript, breaks at runtime
window.document.body.appendChild({nodeName:"div", namespaceURI:null, ...document.body})
Respuesta #1
No existe una bandera del compilador para hacer que el compilador se comporte de manera nominal, de hecho en principio no puede existir tal bandera ya que eso rompería muchos escenarios de JavaScript.
Hay varias técnicas que se utilizan generalmente para emular algún tipo de tipado nominal. Los tipos con marca generalmente se usan para tipos primitivos (elige uno de aquí como ejemplo), para clases una forma común es agregar un campo privado (un campo privado no será emparejado estructuralmente por nada excepto esa definición exacta de campo privado).
Usando el último enfoque, su muestra inicial podría escribirse como:
// there is a class
class MyClassTest {
private _useNominal: undefined; // private field to ensure nothing else is structually compatible.
foo():void{}
bar():void{}
}
// and a function which accepts instances of that class as a parameter
function someFunction(f:MyClassTest):void{
if (f){
if (!(f instanceof MyClassTest)) {
console.log("why")
}
}
}
someFunction({foo(){}, bar(){}, _useNominal: undefined}) //err
Para tu escenario específico, de usar append, no creo que podamos hacer nada ya que Node está definido en lib.d.ts como una interfaz y un const, por lo que aumentarlo con un campo privado es casi imposible (sin cambios en lib.d.ts), e incluso si pudiéramos, potencialmente podría romper mucho código y definiciones existentes.
