Универсальные машинописные тексты |
Привет, волшебники кодирования! Я хочу кратко написать об использовании Typescript и, в частности, об использовании дженериков при создании многоцелевых функций. Чем больше я играю с ними, тем сильнее я понимаю, что они сильнее, поэтому я хотел бы поделиться этой силой со всеми вами.
Так что же?
Generic позволяет вам передавать типы в функцию (или даже в компонент React), что позволит вам обеспечить правильную типизацию внутри функции в соответствии со схемой, с которой вы работаете. Пример выразит лучше, чем слова.
Вот базовая функция javascript, которая будет принимать массив значений и возвращать массив типа {value:any, label:string}
, массив объектов с лучшими практиками при присвоении значений раскрывающемуся списку. Функция также будет сортировать массив, поэтому я назову ее formatAndSortList
:
const formatAndSortList = (list, valueKey, labelKey) => {
return list
.map((item) => {
const value =
typeof valueKey === "function" ? valueKey(item) : item[valueKey];
const label =
typeof labelKey === "function" ? labelKey(item) : item[labelKey];
return { value, label };
})
.sort((a, b) => (a.label.toLowerCase() < b.label.toLowerCase() ? -1 : 1));
};
valueKey и labelKey также могут быть функцией, которая позволяет пользователю возвращать все, что он хочет, из отдельного объекта. Давайте напечатаем это:
interface User {
userId: number;
firstName: string;
lastName: string;
}
interface Option {
value: any;
label: string;
}
const formatAndSortList = (
list: any[],
valueKey: any | ((item: any) => any),
labelKey: string | ((item: any) => string)
): Option[] => {
return list
.map((item) => {
const value =
typeof valueKey === "function" ? valueKey(item) : item[valueKey];
const label =
typeof labelKey === "function"
? labelKey(item)
: (item[labelKey] as string);
return { value, label };
})
.sort((a, b) => (a.label.toLowerCase() < b.label.toLowerCase() ? -1 : 1));
};
const users: User[] = [
{ userId: 1, firstName: "Sean", lastName: "Hurwitz" },
{ userId: 2, firstName: "James", lastName: "Baxter" },
{ userId: 3, firstName: "Melody", lastName: "Brain" },
{ userId: 4, firstName: "Nelson", lastName: "Mandela" },
];
const formattedUsers = formatAndSortList(
users,
"userId",
(user) => `${user.firstName} ${user.lastName}`
);
console.log("formattedUsers", formattedUsers);
Это работает, отлично, но что, если я попробую это:
const formattedUsers = formatAndSortList(
users,
"userId",
(user) => `${user.names.firstName} ${user.names.lastName}`
);
Я получил бы ошибку времени выполнения, потому что names
объект не существует. Так что набор текста не так совершенен. Как сообщить функции, какой именно массив я использую?
Дженерики спешат на помощь!
Вот измененная функция:
const formatAndSortList = <Type extends { [x: string]: any }>(
list: Type[],
valueKey: keyof Type | ((item: Type) => any),
labelKey: keyof Type | ((item: Type) => string)
): Option[] => {
return list
.map((item) => {
const value =
typeof valueKey === "function" ? valueKey(item) : item[valueKey];
const label =
typeof labelKey === "function"
? labelKey(item)
: (item[labelKey] as string);
return { value, label };
})
.sort((a, b) => (a.label.toLowerCase() < b.label.toLowerCase() ? -1 : 1));
};
Мы объявляем <Type>
generic перед нашим списком аргументов в круглых скобках, а затем мы можем ввести наш список аргументов, используя его. Примечание: переменная Type
можно назвать как угодно
Теперь у вас есть отличный intellisense (по крайней мере, в VSCode) для вашей функции:
потому что Typescript знает, так как вы прошли через массив типа User
для первого аргумента, что Type
отображается как User
и может быть идентифицирован в следующих двух аргументах на лету. Как по волшебству!
Так что, если вы попробуете неприятный трюк выше:
Вы получите компилятор ошибка вместо этого избавит вас от многих разочаровывающих белых экранов в ваших проектах!
Обобщения также работают при описании интерфейсов. Например:
interface User {
userId: number;
firstName: string;
lastName: string;
}
interface Project {
id: string;
name: string;
}
interface Pagination {
take: number;
skip: number;
total: number;
}
interface UsersPaged {
pagination: Pagination;
data: User[];
}
interface ProjectsPaged {
pagination: Pagination;
data: Project[];
}
В идеале я хотел бы иметь полезную нагрузку разбивки на страницы для каждой схемы в моем проекте. Трудно создать {Schema}Paged interface
каждый раз. Вместо этого я могу иметь общую схему:
interface PaginationSchema<Schema> {
pagination: Pagination;
data: Schema[];
}
И вызовите его как таковой:
const usersPaged: PaginationSchema<User> = {
pagination: { take: 1, skip: 0, total: 1 },
data: [{ userId: 3, firstName: "s", lastName: "h" }],
};
Заключение
Иногда я просто сижу в благоговении перед силой в моих руках. Это как раз то, в чем заключается радость кодирования. Надеюсь, вам понравилось это введение в дженерики, и я надеюсь, что вы попробуете его в своем следующем проекте! Дайте мне знать, что вы думаете в комментариях ниже.
До следующего раза, продолжайте кодить!
~ Шон