typescript - Как исключить только свойства getter из типа в машинописи




typescript2.0 (2)

Методы получения в классе являются свойствами только для чтения, поэтому имеет смысл выдавать ошибку типа из следующего кода.

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

Есть ли способ, как привести только доступные для записи свойства для ввода и исключения всех получателей?

пример на детской площадке


Хотя readonly напрямую не влияет на присваиваемость типов, это влияет на их идентичность. Чтобы проверить, являются ли два типа идентичными, мы можем использовать (1) правило назначаемости для условных типов, которое требует, чтобы типы после extends были идентичными, или (2) процесс вывода для типов пересечения, который выбрасывает идентичные типы из обоих стороны. Затем мы просто используем сопоставленные типы, как в ответе Тициана Черникова-Драгомира, чтобы посмотреть на каждое свойство 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];
    }
}

Отредактируйте Смотрите @ 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
})




typescript2.0