泛型中的Typescript混淆:`keyof`、`extends keyof`、`typeof`等等

8
class Car {
    engine:number;
    detials:{
        good:'Boy'
    }
}

构造函数 new ModelProperty<Car>('engine',22); 中的 ModelProperty<T> 类应当按照预期工作,因为 engineCar 的一个属性,22engine 相同类型,即 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'
  1. 如果是engine V,应该表示数值。但是在函数do中的那行代码会出错。
  2. 构造函数不应该是<Car,'engine'>,而只应该是<Car>。它的后续属性应该根据参数默认设置。
  3. 如何使其适用于嵌套属性,例如new ModelProperty<Car>(['details','good'],'Girl')
1个回答

4
对于第一个问题,你的方法在 V 参数上存在问题,因为你指定了它的默认值,但这并不意味着 V 必须扩展 T[P],只是这是默认值,你可以使用任何类型参数来调用构造函数。在适当的地方使用 T[P],即使你正确地限制了它 (V extends T[P] = T[P]),编译器仍然无法正确地跟踪 V 是否可分配给 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]
    }
}

关于您提到的第二个问题,这是类型参数和推断方式的不幸副作用。如果您为泛型参数指定了默认值,则该默认值将被使用,并且不会进行推断。如果您不为 K 指定默认值,则无法仅指定 T 的值,还必须指定 K。简单的解决方法是使用两个函数的方法:

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'

关于嵌套路径的第三个问题,类不能有可变数量的类型参数,因此您可以选择为每个路径长度创建专用类。

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

或者,您可以创建一个单一的重载函数,返回一个实例ModelProperty,其中唯一的类型参数是最后一个属性的值,路径为string[]。在创建实例时获得类型安全性,但在此之后信息将丢失。

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接