typescript - Guardia de tipo de propiedad de clase basado en el valor de la propiedad de la instancia
Cómo hacer un ejemplo de clase para inferir el tipo config basado en la verificación del valor de la instancia animalType:
enum Animal {
BIRD = 'bird',
DOG = 'dog',
}
type Base = {
id: number
}
// Object example
type Smth = Base &
(
| {
animalType: Animal.BIRD;
config: number;
}
| {
animalType: Animal.DOG;
config: string;
}
);
// type guards working
const smthObj: Smth = {
id: 1,
animalType: Animal.BIRD,
config: 1
};
// should be error
const smthObj2: Smth = {
id: 1,
animalType: Animal.BIRD,
config: 'x'
};
if (smthObj.animalType === Animal.BIRD) {
smthObj.config = 1;
smthObj.config = 'x'; // should be error
}
// How to make it work the same for class?
class myClass {
id: number;
animalType: Animal;
// this should be based on Animal type
// number for bird and string for dog
config: number | string;
constructor(id: number, animalType: Animal, config: number | string) {
this.id = id;
this.animalType = animalType;
this.config = config
}
}
const smthClass: myClass = 1 as any
// I need to make only this check to work
if (smthClass.animalType === Animal.BIRD) {
smthClass.config = 1;
smthClass.config = 'x'; // should be error
}
Sandbox de Typescript.
Respuesta #1
Los argumentos de la clase deberían ser parte de una estructura de datos.
Este enfoque es seguro y fácil
enum Animal {
BIRD = 'bird',
DOG = 'dog',
}
type Base = {
id: number
}
// Object example
type Smth = Base &
(
| {
animalType: Animal.BIRD;
config: number;
}
| {
animalType: Animal.DOG;
config: string;
}
);
// How to make it work the same for class?
class myClass {
constructor(public props: Smth) {
this.props = props;
}
}
const smthClass: myClass = 1 as any
if (smthClass.props.animalType === Animal.BIRD) {
smthClass.props.config = 1;
smthClass.props.config = 'x'; // should be error
}
ACTUALIZACIÓN
enum Animal {
BIRD = 'bird',
DOG = 'dog',
}
// How to make it work the same for class?
type Either<R> = R extends Animal.DOG ? string : number;
class myClass<T extends Animal> {
id: number;
animalType: T;
config: Either<T>
constructor(id: number, animalType: T, config: Either<T>) {
this.id = id;
this.animalType = animalType;
this.config = config
}
}
const smthClass: myClass<Animal.BIRD> = 1 as any
if (smthClass.animalType === Animal.BIRD) {
smthClass.config = 1;
smthClass.config = 'x'; // should be error
}
Respuesta #2
Usa genéricos.
enum Animal {
BIRD = 'bird',
DOG = 'dog',
}
class myClass<T extends Animal = Animal> {
id: number;
animalType: T;
config: T extends Animal.BIRD ? number : T extends Animal.DOG ? string : never; // this should be based on Animal type
constructor(id: number, animalType: T, config: T extends Animal.BIRD ? number : T extends Animal.DOG ? string : never) {
this.id = id;
this.animalType = animalType;
this.config = config
}
}
const birdClass = new myClass(1, Animal.BIRD, 1);
if (birdClass.animalType === Animal.BIRD) {
birdClass.config = 1;
birdClass.config = 'x'; // should be error
}
const dogClass = new myClass(1, Animal.DOG, 'x');
if (dogClass.animalType === Animal.DOG) {
dogClass.config = 1; // should be error
dogClass.config = 'x';
}
const smthClass: myClass<Animal.BIRD> = 1 as any;
if (smthClass.animalType === Animal.BIRD) {
smthClass.config = 1;
smthClass.config = 'x'; // should be error
}
// Type guard function example for any class
function isBirdClass(smth: myClass): smth is myClass<Animal.BIRD> {
return smth.animalType === Animal.BIRD ? true : false
}
function isDogClass(smth: myClass): smth is myClass<Animal.DOG> {
return smth.animalType === Animal.DOG ? true : false
}
const unknownClass: myClass = 1 as any;
if (isBirdClass(unknownClass)) {
unknownClass.config = 1;
unknownClass.config = 'x'; // should be error
}
if (isDogClass(unknownClass)){
unknownClass.config = 1; // should be error
unknownClass.config = 'x';
}
Véalo en TypeScript Playground.
Nota:
- Usando genéricos, ni siquiera necesitas una protección de tipo porque los genéricos capturarán automáticamente los errores de tipo.
