typescript - Usar atributo de interfaz derivada en clase derivada sin aserción
Tengo una interfaz base que define un tipo de objeto en mi código y múltiples otras interfaces que la extienden. Esta interfaz base será un atributo de una clase base y luego la clase derivada tendrá la interfaz extendida como atributos. ¿Cómo puedo usar las interfaces extendidas en la clase derivada sin tener que usar siempre la palabra clave as en TypeScript?
interface BaseData {
id: string;
}
interface DerivedDataA extends BaseData {
derivedFieldA: number;
}
interface DerivedDataB extends BaseData {
derivedFieldB: boolean;
}
class BaseClass {
data: BaseData;
constructor(data: BaseData) {
this.data = data;
}
getId() {
console.log(this.data.id);
}
compute() {
// implement in derived class
}
}
class DerivedClassA extends BaseClass {
compute() {
// This is the issue I am having, how to use the derived interface without `as`
console.log((this.data as DerivedDataA).derivedFieldA);
}
}
class DerivedClassB extends BaseClass {
compute() {
// This is the issue I am having, how to use the derived interface without `as`
console.log((this.data as DerivedDataB).derivedFieldB);
}
}
He intentado usar tipos genéricos, pero no parece encajar como solución. ¿Cómo haría uno esto en otro idioma que no sea TypeScript? ¿Además, es esto un patrón incorrecto/mala práctica? Gracias
Respuesta #1
Si tu intención es restringir el tipo de una propiedad de una subclase sin tener ningún cambio en tiempo de ejecución, puedes hacerlo con el modificador de propiedad declare introducido en TypeScript 3.7:
class DerivedClassA extends BaseClass {
declare data: DerivedDataA; // declared here
compute() {
console.log(this.data.derivedFieldA); // okay
}
}
class DerivedClassB extends BaseClass {
declare data: DerivedDataB; // declared here
compute() {
console.log(this.data.derivedFieldB);
}
}
Actualizar :
Si recuerdo correctamente, Java no te permite hacer que los campos sean covariantes (consulta el artículo de Wikipedia sobre varianza para una explicación) porque son de lectura y escritura y el lenguaje es más estricto que TypeScript. TypeScript adopta el punto de vista (no seguro) de que las escrituras de propiedades no necesitan ser verificadas de forma tan estricta. Esto es conveniente, pero conduce a posibles errores en tiempo de ejecución que no se detectan:
new DerivedClassA({ id: "oops" })
.data.derivedFieldA.toFixed(2) // no compiler error, but
// at runtime 💥 derivedFieldA is undefined!
Puedes hacer que esto sea un poco menos probable en TypeScript con algunos ajustes:
class DerivedClassA extends BaseClass {
declare data: DerivedDataA;
// explicit constructor (do the same for B)
constructor(data: DerivedDataA) {
super(data);
}
compute() {
console.log(this.data.derivedFieldA);
}
}
new DerivedClassA({ id: "oops" }); // error!
// -------------> ~~~~~~~~~~~~~~
// Property 'derivedFieldA' is missing
Pero todavía no es completamente seguro:
const a = new DerivedClassA({ id: "okay", derivedFieldA: 123 });
const base: BaseClass = a; // acceptable supertype assignability
base.data = { id: "wait a second" }; // acceptable for BaseClass,
// but this is a DerivedClassA, right? which means:
a.data.derivedFieldA.toFixed(2) // again, no compiler error, but
// runtime error! 💥 derivedFieldA is undefined
Pero así es TypeScript; estos errores resultan ser lo suficientemente raros en el código del mundo real como para que el equipo de TS decidiera permitir falsos negativos y hacer la programación más fácil. Consulta esta respuesta para más información sobre la interacción particular entre la covarianza de propiedades, la creación de alias y la escritura de propiedades. La forma "correcta" de hacer esto sería usar marcadores de varianza específicos y tal vez comodines genéricos como en Java, pero el equipo de TS piensa que son demasiado difíciles de usar para la mayoría de los programadores.
De todas formas, este tipo de reducción de propiedades de subtipos no es un anti-patrón ni una mala práctica en TypeScript, pero si estás acostumbrado a un sistema de tipos seguro (o más cercano a seguro) como en Java (que tiene algunos agujeros como este, la covarianza de los arrays integrados es uno grande) podría hacerte sentir incómodo. El consejo probablemente sea simplemente "úsalo pero trata de tener cuidado con ello".
Enlace de Playground al código
