Advanced types
Sara Lissette
Desarrolladora Fullstack en
@LissetteIbnz
interface FormState {
name: string;
age: number;
country: string;
}
export const SampleComponent: React.FC = () => {
const [formState, setFormState] = React.useState<FormState>(null);
// fieldName: "name" | "age" | "country"
const handleOnChange = (fieldName: keyof FormState) => (
event: React.ChangeEvent<HTMLInputElement>,
) => setFormState(oldValues => ({ ...oldValues, [fieldName]: event.target.value }));
return (
<>
<input onChange={handleOnChange("name")} value={formState.name}/>
<input onChange={handleOnChange("age")} value={formState.age}/>
<input onChange={handleOnChange("country")} value={formState.country}/>
</>
);
};
{ keyof T }
Crea un tipo string literal con las claves de T
interface FormState {
id: number;
name: string;
age: number;
country: string;
}
type Validation = Pick<FormState, "name" | "age">;
···
const handleOnChange = (fieldName: keyof FormState) => (
event: React.ChangeEvent<HTMLInputElement>,
) => {
···
if (fieldName !== "id" && fieldName !== "country") { // Error si omitimos esta guarda
fieldValidation(fieldName, formState);
}
};
···
function fieldValidation<T extends FormState>(fieldName: keyof Validation, values: T): void;
{ Pick <T, K> }
Construye un tipo con todas las propiedades de T que son claves de K y existen en T
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
{ Omit <T, K**> }
Construye un tipo con todas las propiedades de T excepto las que son claves de K
type Omit<T, K extends keyof any> =
Pick<T, Exclude<keyof T, K>>;
import { SelectProps as SelectPropsMUI } from "@material-ui/core/Select";
interface SelectProps extends Omit<SelectPropsMUI, "multiple" | "multiline"> {
label?: string;
options: string[];
readOnly?: boolean;
}
const SelectComponent: React.FC<SelectProps> = ({
classes,
disabled,
label,
onChange,
options,
readOnly,
required,
value = "",
...other
}) => {
···
Omit paso a paso:
T = "a" | "b" | "c"
K = "b"
Exclude<T, K> = <"a" | "b" | "c", "b"> = "a" | "c"
Pick<T, result Exclude<T, K>> = <"a" | "b" | "c", "a" | "c"> = "a" | "c"
{ OmitOwn <T, K> }
Tipo personalizado que construye un tipo con todas las propiedades de T que son claves de K y existen en T
type Omit<T, K extends keyof T> =
Pick<T, Exclude<keyof T, K>>;
interface FormState {
name: string;
age: number;
country: string;
}
type Validation = OmitOwn<FormState, "age" | "country">;
{ Partial <T> }
Construye un tipo con todas las propiedades de T establecidas como opcionales
const mapUserAMToVM = (user: User): UserVM =>
user && { age: user.age, email: user.email, lastName: user.lastName, name: user.name };
describe("mapUserAMToVM", () => {
const testUser = ({
age = 20,
email = "Irrelevant email",
id = 1,
lastName = "Irrelevant lastName",
name = "Irrelevant name",
}: Partial<User>): User => ({ age, email, lastName, name, id });
it("should be a valid UserVM when pass a valid user", () => {
const newUser: User = testUser({ id: 5, email: "test@email.com" });
const result = mapUserAMToVM(newUser);
expect(result).toEqual({
age: 20,
email: "test@email.com",
lastName: "Irrelevant lastName",
name: "Irrelevant name",
});
});
});
type Partial<T> = { [P in keyof T]?: T[P] };
{ Required <T> }
Construye un tipo con todas las propiedades de T establecidas como requeridas
interface UserVM {
name: string;
email: string;
age?: number; // optional
country?: string; // opcional
}
type UserAPI = Required<UserVM>;
const setUser = (user: UserAPI): void => {
fetch("https://api.gofio.com/user", {
method: "post",
body: JSON.stringify(user),
})
.then(response => response.json())
.then(data => notify(data));
};
const user: UserVM = {
email: "email@email.com",
name: "Sample",
};
setUser(user); /** ERROR Argument of type 'UserVM' is not assignable to parameter of type 'Required<UserVM>'.
Property 'age' is optional in type 'UserVM' but required in type 'Required<UserVM>' */
type Required<T> = { [P in keyof T]-?: T[P] };
{ Readonly <T> }
Construye un tipo con el conjunto de propiedades de T establecidas como readonly
interface FormState {
readonly id: number; // <- sólo lectura
name: string;
age: number;
country: string;
}
type FormStartReadOnly = Readonly<FormState>;
type FormStartReadOnly = {
readonly id: number;
readonly name: string;
readonly age: number;
readonly country: string;
}
type Readonly<T> = { readonly [P in keyof T]: T[P] };
{ Record <K, T> }
Construye un tipo con un conjunto de propiedades especificadas en K del tipo T
interface Data {
root: boolean;
path: string;
auth: boolean;
timeExpire: number;
}
type SampleUsers = "admin" | "user" | "guest";
const sampleUsers: Record<SampleUsers, Data> = {
admin: { root: true, auth: true, path: "/users/admin", timeExpire: 2 },
user: { root: false, auth: true, path: "/users/user", timeExpire: 5 },
guest: { root: false, auth: false, path: "/users/public", timeExpire: 0 },
};
type Record<K extends keyof any, T> = { [P in K]: T };
{ ReturnType <function> }
Construye un tipo con el retorno de la función
const ClientFactory = (client: FormState) => ({
id: Math.random(),
date: new Date(),
active: true,
...client,
});
type ClientData = ReturnType<typeof ClientFactory>;
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any;
{ Tipos mapeados y genéricos }
Ejemplo de cómo construir un nuevo tipo a partir uno existente y cambiar sus propiedades
interface FormState {
name: string;
email: string;
}
interface ValidationResult<T> {
hasError: boolean;
message: "Field is required" | "";
value: T;
}
type ValidationField<T> = { [P in keyof T]: ValidationResult<T[P]> };
const requiredFieldValidation = (state: FormState): ValidationField<FormState> =>
Object.keys(state)
.map(key => ({
[key]: {
hasError: !state[key],
message: state[key] ? "" : "Field is required",
value: state[key],
} as ValidationResult<FormState>,
}))
.reduce((accumulator, current) => ({ ...accumulator, ...current }), {} as ValidationField<FormState>);
type ValidationField<T> =
{ [P in keyof T]: ValidationResult<T[P]> };
+ Object {
+ "email": Object {
+ "hasError": true,
+ "message": "Field is required",
+ "value": "",
+ },
+ "name": Object {
+ "hasError": false,
+ "message": "",
+ "value": "Irrelevant name",
+ },
+ }
Preguntas, dudas, cervezas...?
GRACIAS
Tipos avanzados con TypeScript
By Sara Lissette
Tipos avanzados con TypeScript
Presentación para el #SummerTech2019 de GDG Tenerife en colaboración con AdaLoveDev - agosto2019
- 1,122