typescript - Cómo excluir propiedades de getter solo del tipo en mecanografiado
typescript2.0 (2)
Los captadores en la clase son propiedades de solo lectura, por lo que el error de tipo de lanzamiento desde el siguiente código tiene sentido.
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.
}
}
¿Hay una manera de convertir propiedades escritables solo para escribir y excluir a todos los captadores?
Si bien
readonly
no afecta directamente si los tipos son asignables, sí afecta si son idénticos.
Para probar si dos tipos son idénticos, podemos abusar de (1) la regla de asignabilidad para los tipos condicionales, que requiere que los tipos posteriores sean idénticos, o (2) el proceso de inferencia para los tipos de intersección, que arroja tipos idénticos de ambos lados
Luego solo usamos tipos mapeados como en la respuesta de Tiziano Cernicova-Dragomir para ver cada propiedad de
Car
por turno y ver si es idéntica a una versión mutable de sí misma.
// 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];
}
}
Edite See @ matt-mccutchen para una solución interesante a este problema.
Respuesta original
readonly
es un modificador bastante débil, ya que no afecta la asignabilidad.
Entonces, por ejemplo, puede asignar un objeto con propiedades de solo
readonly
a aquellas con esas mismas propiedades mutables y el compilador no se quejará:
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
Este es un problema conocido, documentado here .
Debido a esta regla de asignabilidad, no hay manera de distinguir en tipos condicionales la diferencia entre un campo de solo lectura y uno mutable.
Una solución es agregar algo extra al tipo de campos de solo lectura. Esto no afectará la forma en que puede usar el campo, pero nos dará un gancho para eliminar la clave.
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
})