Confusión de TypeScript en genéricos `keyof`, `extends keyof`, `typeof` y otros
class Car {
engine:number;
detials:{
good:'Boy'
}
}
La clase ModelProperty<T> cuando se construye con new ModelProperty<Car>('engine',22); debería funcionar ya que engine es una propiedad de Car y 22 es del mismo tipo que engine, es decir, number.
export class ModelProperty<T, P extends keyof T, V = T[P]> {
constructor(public name: P, public value: V) { }
fun(t: T){
let value: any = t[this.name]; // Should not be any
let valueWhyError: V = t[this.name]; //Error.. Why?
}
}
let engine2 = new ModelProperty<Car,'engine'>('engine','22'); // Gives error as '22' should be number.. working great.
let engine1 = new ModelProperty<Car,'engine'>('engine',2); // But there is repeatation 'engine', 'engine'
- En caso de
engine,Vdebería significarnumber. Pero esa línea en la funcióndoda error. - El constructor no debería ser
<Car,'engine'>, sino solo<Car>. Sus propiedades siguientes deberían ser por defecto dependiendo del argumento. - Cómo hacer que funcione para una propiedad anidada como
new ModelProperty<Car>(['details','good'],'Girl').
Respuesta #1
Para el primer problema, el inconveniente de tu enfoque para el parámetro V es que especificas un valor predeterminado para él, pero eso no significa que V DEBA extender T[P], solo que ese es el valor predeterminado; puedes invocar el constructor con prácticamente cualquier tipo de parámetro para V. Simplemente usa T[P] donde sea adecuado, ya que incluso si lo restringes correctamente (V extends T[P]=T[P]), el compilador aún no podrá seguir correctamente que V es asignable desde T[P].
export class ModelProperty<T, P extends keyof T> {
constructor(public name: P, public value: T[P]) { }
fun(t: T){
let value = t[this.name]; // is T[P]
}
}
En cuanto a su segundo problema de repetición, esto es un efecto secundario desafortunado de la forma en que funcionan los parámetros de tipo y la inferencia; si especifica un valor predeterminado para el parámetro genérico, ese valor predeterminado se usará y no se realizará ninguna inferencia. Si no especifica el valor predeterminado para K, no puede especificar solo el valor para T, también debe especificar K. La solución sencilla es usar un enfoque de dos funciones:
export class ModelProperty<T, P extends keyof T> {
constructor(public name: P, public value: T[P]) { }
static for<T>() {
return function <P extends keyof T>(name: P, value: T[P]){
new ModelProperty<T, P>(name, value);
}
}
}
const carModelCreator = ModelProperty.for<Car>();
let engine2 = carModelCreator('engine','22'); // Gives error as '22' should be number.. working great.
let engine1 = carModelCreator('engine',2); // But there is repeatation 'engine', 'engine'
En cuanto al tercer tema de las rutas anidadas, las clases no pueden tener un número variable de parámetros de tipo, por lo que tienes la opción de crear clases dedicadas para cada longitud de ruta.
export class ModelProperty2<T, P extends keyof T, P2 extends keyof T[P]> {
constructor(public name: [P, P2], public value: T[P][P2]) { }
static for<T>() {
return function <P extends keyof T, P2 extends keyof T[P]>(name: [P, P2], value: T[P][P2]){
new ModelProperty2<T, P, P2>(name, value);
}
}
}
const carModelCreator = ModelProperty2.for<Car>();
let engine2 = carModelCreator(['detials', 'good'],'22'); //error
let engine2 = carModelCreator(['detials', 'good'],'Boy'); //ok
O si quieres, puedes crear una única función sobrecargada que devuelva una instancia de ModelProperty donde el único parámetro de tipo sea el valor de la última propiedad, y la ruta sea string[]. Obtienes seguridad de tipo cuando creas la instancia, pero la información se pierde después
export class ModelProperty<T, V> {
constructor(public name: string[], public value: V) { }
static for<T>() {
function helper<P extends keyof T, P2 extends keyof T[P]>(name: [P, P2], value: T[P][P2])
function helper<P extends keyof T>(name: [P], value: T[P])
function helper(name: string[], value: any){
return new ModelProperty<T, any>(name, value);
}
return helper;
}
}
const carModelCreator = ModelProperty.for<Car>();
let engine1 = carModelCreator(['engine'], 22); // ok
let engine2 = carModelCreator(['detials', 'good'],'Boy'); //ok
