javascript - Ampliando miembro de objeto de subclase
Estoy construyendo Objetos de Página para mis pruebas automatizadas, y estoy haciendo algo así:
abstract class Page {
selectors: {
// Here are selectors which are common to all my pages
genericSelector: "...",
commonContainer: {
aButton: "..."
}
}
// methods that are common to all my pages
}
class MyPage extends Page {
// Here I want my page specific selectors
// *plus* my generic selectors
selectors: {
...this.selectors,
mySpecificSelector: "..."
}
}
El código anterior funciona bien en tiempo de ejecución: puedo usar correctamente todos mis selectores en mi código. Sin embargo, Typescript se queja en MyPage.ts de que:
Property 'selectors' in type 'MyPage' is not assignable to the same property in base type 'Page.
Básicamente porque TS no sabe que estoy extendiendo la propiedad de la clase base en la subclase.
Ahora, lo siguiente es lo que intenté:
Una nota adicional
Mi tipo para la propiedad selector se ve así:
type Selector = string | SelectorFactory | { selector: string, type: "css" | "xpath" }
type SelectorFactory = (...args: any[]) => string;
type SelectorTree = { [k: string]: SelectorTree | Selector }
Enlace conveniente al Playground de TS
Lo que intenté
Primer intento
class MyPage extends Page {
selectors: {
...this.selectors as InstanceType<typeof Page>["selectors"]
}
}
Esto, entonces se queja:
'selectors' implicitly has type 'any' because it does not have a type annotation and is referenced directly or indirectly in its own initializer.
Entonces tengo que hacer:
selectors: SelectorTree = // ...
Entonces vuelvo al punto de partida, ya que se queja de que faltan algunas propiedades de la clase base. Si agrego : SelectorTree a mi clase base, entonces no se queja, pero no obtengo la función de autocompletado adecuada y incluso obtengo errores (por ejemplo, si intento llamar a una función de las propiedades de mis selectores, no puedo).
Segundo intento
Supongo que podría hacer que mi miembro selectors de la clase base Page sea estático. Entonces podría hacer:
selectors: {
...Page.selectors,
mySpecificSelector: "..."
}
Esto funciona, pero, bueno, ahora en realidad tengo un miembro estático extra que no quería tener.
Tercer intento, también conocido como "Me rindo"
Podría separar a los miembros y tener algo como baseSelectors en mi clase Page y luego podría usarlos tal cual dentro de la subclase, o esparcirlos en mi objeto selectors.
Pero esto se siente como una derrota.
Respuesta #1
Esto, desafortunadamente, parece ser una limitación de diseño en TypeScript, según microsoft/TypeScript#33899:
Debido a las limitaciones arquitectónicas sobre cómo se marcan los árboles como verificados, esto debe tratarse como una referencia circular a pesar de la afirmación de tipo.
Al parecer, cualquier referencia a this.selectors, sin importar lo indirecta que sea, parece causar un error de circularidad. Hasta ahora, la única solución que he encontrado que te da los tipos correctos sin tener que refactorizar el código emitido es ampliar this a Page (nota: InstanceType<typeof Page> es lo mismo que Page) para obtener un tipo no-any para this.selectors, y luego simplemente suprimir de manera forzada la advertencia de circularidad con //@ts-ignore:
class SubPage extends Page {
//@ts-ignore circularity warning
selectors = { ...(this as Page).selectors, foo: "bar", open: () => "" }
}
Puede verificar que las instancias de SubPage se comporten como se espera:
const p = new SubPage();
p.selectors;
// SubPage.selectors: {
// foo: string;
// open: () => string;
// test: string;
// }
p.selectors.open().toUpperCase(); // okay
¡Eso funciona! Pero...
⚠ ALERTA ⚠ Me siento muy incómodo cada vez que alguien sugiere //@ts-ignore porque si el error suprimido tiene algún otro efecto desagradable en tu código, estos seguirán existiendo. No vi ningún efecto de este tipo con el código de ejemplo anterior, pero no puedo estar seguro de que no estén presentes. Si tu mecánico de automóviles solucionara una luz encendida de "check engine" cubriendo la luz indicadora para que ya no la puedas ver, tal vez quieras empezar a buscar a otro mecánico, aunque sea solo para una segunda opinión. 😅
Enlace de Playground al código
