【问题标题】:How to exclude getter only properties from type in typescript如何从打字稿中的类型中排除仅吸气剂的属性
【发布时间】:2019-02-25 20:05:15
【问题描述】:

类中的 Getter 是只读属性,因此从以下代码中抛出类型错误是有意义的。

class Car {
    engine: number;
    get hp() {
        return this.engine / 2;
    }
    get kw() {
        return this.engine * 2;
    }
}

function applySnapshot(
    car: Car,
    snapshoot: Partial<Car> // <-- how to exclude readonly properties?
) {
    for (const key in snapshoot) {
        if (!snapshoot.hasOwnProperty(key)) continue;
        car[key as keyof Car] = snapshoot[key as keyof Car];
        // Cannot assign to 'hp' because it is a constant or a read-only property.
    }
}

有没有办法将只可写的属性转换为类型并排除所有 getter?

example in playground

【问题讨论】:

    标签: typescript types getter typescript2.0


    【解决方案1】:

    编辑 请参阅 @matt-mccutchen 了解此问题的有趣解决方法。

    原答案

    readonly 是一个相当弱的修饰符,因为它不会影响可分配性。因此,例如,您可以将具有 readonly 属性的对象分配给具有相同可变属性的对象,编译器不会抱怨:

    let roCar: Partial<Car> = { hp: 10 } // we can assign a  mutable object to a referecne with a readonly property
    roCar.hp = 10; // error hp is readonly
    
    //But we can also assign an object with a readonly property to a fully mutable version of it 
    let allMutableCar: { -readonly [P in keyof Car]: Car[P] } = new Car();
    allMutableCar.hp = 10; // No compile time error
    

    这是一个已知问题,记录在 here

    由于这个可分配性规则,无法在条件类型中区分只读字段和可变字段之间的区别。

    一种解决方法是在只读字段的类型中添加一些额外的内容。这不会影响您如何使用该字段,但它会给我们一个钩子来删除密钥。

    type readonly = { readonly?: undefined };
    class Car {
        engine!: number;
        get hp() : number & readonly {
            return this.engine / 2;
        }
        get kw() : number & readonly {
            return this.engine * 2;
        }
    }
    
    type NoReadonlyKeys<T> = { [P in keyof T]: 'readonly' extends keyof T[P] ? never : P }[keyof T]
    
    type PartialNoReadonly<T> = Partial<Pick<T, NoReadonlyKeys<T>>>  
    type Mutable<T> = { -readonly [P in keyof T]: T[P] }
    function applySnapshot(
        car: Car,
        snapshoot: PartialNoReadonly<Car>
    ) {
        const mutableCar: Mutable<Car> = car; // erase readonly so we can mutate
        for (const key in snapshoot) {
            let typedKey = key as keyof typeof snapshoot
            if (!snapshoot.hasOwnProperty(key)) continue;
            mutableCar[typedKey] = snapshoot[typedKey] as any;
        }
    }
    
    applySnapshot(new Car(), {
        engine: 0
    })
    applySnapshot(new Car(), {
        hp: 0 /// error
    })
    

    【讨论】:

    • 我有一个问题,如果我想让 getter 返回 null,但是 null &amp; readonly 不起作用,只会变成 readonly。有解决办法吗?
    【解决方案2】:

    虽然readonly 不会直接影响类型是否可赋值,但会影响它们是否相同。为了测试两个类型是否相同,我们可以滥用(1)条件类型的可分配性规则,它要求extends之后的类型相同,或者(2)交集类型的推理过程,它会抛出相同的类型从双方。然后,我们只使用 Titian Cernicova-Dragomir 的回答中的映射类型,依次查看 Car 的每个属性,看看它是否与自身的可变版本相同。

    // https://github.com/Microsoft/TypeScript/issues/27024#issuecomment-421529650
    type IfEquals<X, Y, A, B> =
        (<T>() => T extends X ? 1 : 2) extends
        (<T>() => T extends Y ? 1 : 2) ? A : B;
    
    // Alternatively:
    /*
    type IfEquals<X, Y, A, B> =
        [2] & [0, 1, X] extends [2] & [0, 1, Y] & [0, infer W, unknown]
        ? W extends 1 ? B : A
        : B;
    */
    
    type WritableKeysOf<T> = {
        [P in keyof T]: IfEquals<{ [Q in P]: T[P] }, { -readonly [Q in P]: T[P] }, P, never>
    }[keyof T];
    type WritablePart<T> = Pick<T, WritableKeysOf<T>>;
    
    class Car {
        engine: number;
        get hp() {
            return this.engine / 2;
        }
        get kw() {
            return this.engine * 2;
        }
    }
    
    function applySnapshot(
        car: Car,
        snapshoot: Partial<WritablePart<Car>>
    ) {
        let key: keyof typeof snapshoot;
        for (key in snapshoot) {
            if (!snapshoot.hasOwnProperty(key)) continue;
            car[key] = snapshoot[key];
        }
    }
    

    【讨论】:

    • 这个答案使用了这样一个事实,即只有当 X 和 Y(仅用于 T 的约束)且相同(包括它们的 readonly 属性)时,编译器才会认为这两个通用函数相同),虽然这可行,但似乎有点不一致,我们是否知道(即编译器团队有一些保证)这不会在未来的编译器版本中中断。
    • 不能保证,但我认为这种行为不太可能改变。如果是这样,我添加了另一种可能的方法(这也可能会中断,但有两个选项比有一个更好)。
    • 很公平,我只是想知道,有时团队中的某个人会批准特定于实现的行为(例如,我确定我在某处读到 Ryan Cavanaugh 说 `T & {}` to reduce可以依赖推理站点的优先级)。但是如果我们用得够多,就很难改变了:)
    • 太棒了@MattMcCutchen!我现在敢让你创建一个递归版本的WritablePart :D 本着DeepReadonlyDeepPartial 的精神。你觉得有可能吗?
    • 如果您更愿意使用库,请查看npmjs.com/package/ts-essentials
    【解决方案3】:

    嘿,我的问题可能对你的问题有答案。

    How do you get the type of the object that is cloned from a Class Instance?

    基本上你可以通过这样做排除所有的getter(和函数)

    class Car {
        engine: number = 1;
        get hp() {
            return this.engine / 2;
        }
        get kw() {
            return this.engine * 2;
        }
    }
    
    var snapShot = {...new Car()};
    type CarNoGetters = typeof snapShot; 
    

    那么你的函数会像这样工作:

    function applySnapshot(
        car: Car,
        snapshoot: CarNoGetters
    ) {
    
        for (const key of Object.keys(snapshoot) as Array<keyof typeof snapshoot>) {
            car[key] = snapshoot[key];
        }
    }
    

    我的问题询问如何在不使用 Javascript 的情况下获取类型 CarNoGetters,即。 var snapShot = {...new Car()};

    但如果你不在乎,你可以使用它。

    (注意我使用的是 TS ^3.75)

    ts playground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-07-19
      • 2018-04-05
      • 1970-01-01
      • 1970-01-01
      • 2017-04-23
      • 2017-01-26
      • 2012-01-31
      • 1970-01-01
      相关资源
      最近更新 更多