如何从打字稿中的类型中排除仅吸气剂的属性

时间:2018-09-21 11:57:17

标签: typescript types getter typescript2.0

类中的字母是只读属性,因此以下代码引发类型错误是有意义的。

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.
    }
}

有没有一种方法可以强制转换仅可写属性以键入和排除所有吸气剂?

example in playground

3 个答案:

答案 0 :(得分:13)

虽然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];
    }
}

答案 1 :(得分:4)

修改 有关此问题的解决方法,请参见@ 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
})

答案 2 :(得分:1)

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

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

基本上,您可以通过此操作排除所有吸气剂(和功能)

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