Лучшие практики программирования на TypeScript
В этой статье объясняются лучшие практики программирования на TypeScript.
В этом руководстве рассматриваются практические лучшие практики использования типов TypeScript для уменьшения количества ошибок и написания более читаемого кода.
YouTube Video
Лучшие практики программирования на TypeScript
Главное преимущество TypeScript — это 'предотвращение ошибок с помощью типов и ясное выражение намерений кода'.
Лучшие практики — это не просто правила, а набор принципов для написания безопасного, читаемого и поддерживаемого кода. Далее мы приведем часто используемые лучшие практики TypeScript с практическими примерами.
Избегайте типа any и всегда давайте осмысленные определения типам
Сначала рассмотрим вопрос «избегать использования any и задавать осмысленные типы».
any полностью отключает проверку типов, что сводит на нет смысл использования TypeScript. Вместо использования any для того, чтобы все заработало, важно указывать максимально описательные типы.
1// Bad
2function parse(data: any) {
3 return data.value;
4}Этот код может принимать любое значение, поэтому невозможно предотвратить ошибки во время выполнения.
1// Good
2type ParsedData = {
3 value: string;
4};
5
6function parse(data: ParsedData): string {
7 return data.value;
8}Определяя типы, вы уточняете назначение входных и выходных данных и повышаете безопасность.
Всегда явно определяйте структуры объектов с помощью type или interface.
Далее рассмотрим вопрос «всегда явно определять структуру объектов с помощью type или interface».
Если вы используете объекты произвольно, их структура может стать неясной. Всегда выносите типы отдельно ради повторного использования и удобства поддержки.
1// Bad
2function createUser(user: { name: string; age: number }) {
3 console.log(user.name);
4}Даже в небольших фрагментах кода важно выработать привычку разделять типы.
1// Good
2type User = {
3 name: string;
4 age: number;
5};
6
7function createUser(user: User): void {
8 console.log(user.name);
9}Давая имена типам, вы облегчаете понимание всей кодовой базы.
Используйте объединённые типы (union types), чтобы точно обозначить все возможные состояния.
Если вы используете обычные string или number-типы в условиях, туда могут попасть неожиданные значения. Используя объединённые типы, вы можете представлять только допустимые состояния на уровне типов.
1// Bad
2function setStatus(status: string) {
3 console.log(status);
4}В этом коде бессмысленные строки или некорректные значения не могут быть обнаружены во время компиляции.
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4function setStatus(status: Status): void {
5 console.log(status);
6}С помощью объединённых типов вы можете надёжно устранить 'невозможные состояния' во время компиляции. В результате повысится безопасность условных ветвлений и надёжность кода.
Обрабатывайте null и undefined явно.
Далее рассмотрим вопрос «явно обрабатывать значения null и undefined».
В TypeScript важно отражать в типе возможное отсутствие значения. Если оставлять неопределенность, это может привести к ошибкам во время выполнения.
1type User = {
2 name: string;
3 email?: string;
4};email может не существовать, поэтому это нужно учитывать при обработке.
1function printEmail(user: User): void {
2 if (user.email) {
3 console.log(user.email);
4 }
5}Всегда проверяйте опциональные значения перед их использованием.
Используйте утверждения типов (as) только в крайнем случае.
Далее рассмотрим вопрос «не злоупотреблять утверждениями типов (type assertions)».
Утверждения типов временно обходят проверку типов TypeScript, чтобы объявить: 'Я уверен, что это значение принадлежит этому типу.'. Чрезмерное их использование подрывает безопасность типов.
1// Bad
2const value = input as string;В этом коде, даже если фактическое значение не является строкой, ошибка не возникает, что может привести к ошибкам во время выполнения. Как показано в следующем коде, сначала выберите безопасную проверку с помощью защитников типов (type guards).
1// Good
2function isString(value: unknown): value is string {
3 return typeof value === "string";
4}
5
6if (isString(input)) {
7 console.log(input.toUpperCase());
8}Защитники типов (type guards) — это механизм безопасного определения типа при проверке фактического значения. Отдавая приоритет защитникам типов (type guards) перед утверждениями типов, вы легче предотвратите ошибки во время выполнения.
Не полагайтесь слишком сильно на вывод типов для возвращаемых значений.
Далее рассмотрим вопрос «не чрезмерно полагаться на вывод типов для возвращаемых значений».
Механизм вывода типов в TypeScript мощный, но безопаснее явно указывать возвращаемые типы у публичных функций. Это минимизирует возможное влияние будущих изменений.
1// Bad
2function sum(a: number, b: number) {
3 return a + b;
4}Ясно выражайте намерения даже в небольших функциях.
1// Good
2function sum(a: number, b: number): number {
3 return a + b;
4}Явное указание возвращаемых типов повышает стабильность вашего API.
Безопасно обрабатывайте входные данные, используя тип unknown.
Далее рассмотрим вопрос «безопасно принимать внешний ввод с помощью типа unknown».
Для внешних данных, таких как API, JSON или пользовательский ввод, используйте unknown вместо any. Таким образом вы гарантируете, что все значения проходят валидацию, сохраняя типовую безопасность.
1// Bad
2function handleResponse(data: any) {
3 console.log(data.id);
4}Вот как проверить типы с помощью unknown.
1// Good
2function handleResponse(data: unknown): void {
3 if (
4 typeof data === "object" &&
5 data !== null &&
6 "id" in data
7 ) {
8 console.log((data as { id: number }).id);
9 }
10}unknown нельзя использовать как есть; это тип, который требует валидации. Это особенно эффективно при обработке внешних данных.
Увеличьте выразительность кода, комбинируя небольшие типы.
Далее рассмотрим вопрос «повышать выразительность за счёт комбинации небольших типов».
Определение больших типов сразу снижает читаемость и удобство поддержки. Делите типы на осмысленные части и объединяйте их при необходимости.
1type Id = number;
2
3type UserProfile = {
4 id: Id;
5 name: string;
6};
7
8type UserWithStatus = UserProfile & {
9 status: "active" | "inactive";
10};Относитесь к типам как к компонентам — это помогает структурировать проект.
type и interface
Преимущества interface
type и interface оба могут определять типы, но их назначение и особенности различаются. Используя их по назначению, вы проясняете смысл своих определений типов.
1// Bad
2type User = {
3 id: number;
4 name: string;
5};
6
7type AdminUser = {
8 id: number;
9 name: string;
10 role: "admin";
11};Если дублировать общие части таким образом, ваш код станет хрупким к изменениям.
1// Good
2interface User {
3 id: number;
4 name: string;
5}
6
7interface AdminUser extends User {
8 role: "admin";
9}interface идеально подходит для структур с расширением (extends) и лучше всего выражает «форму» объектов.
Преимущества type
С другой стороны, type более выразителен и подходит для объединённых и пересекающихся типов.
1// Good
2type Status = "idle" | "loading" | "success" | "error";
3
4type ApiResponse<T> =
5 | { status: "success"; data: T }
6 | { status: "error"; message: string };type хорошо подходит для выражения состояний, опций и комбинаций.
Рекомендации по выбору между type и interface
В общем случае используйте interface для структур объектов и контрактов, а type — когда нужна выразительность объединений, пересечений или операций над типами.
Они работают похоже, но важно выбирать в зависимости от смысла существования типа.
Считайте ваши типы частью документации.
Наконец, рассмотрим вопрос «использовать типы в качестве документации».
Правильные определения типов передают больше информации, чем любые комментарии. Важно стремиться к тому, чтобы «спецификацию можно было понять, просто взглянув на типы».
1type ApiError = {
2 code: number;
3 message: string;
4 retryable: boolean;
5};Таким образом, одним из основных преимуществ TypeScript является то, что определения типов могут служить своего рода спецификационной документацией.
Резюме
Лучшие практики TypeScript — это не чрезмерная строгость. Суть в том, чтобы прояснять намерения с помощью типов и писать код, устойчивый к изменениям.
Постепенное внедрение небольших правил в ежедневной работе приносит такие долгосрочные эффекты, как «проще ревью», «меньше ошибок» и «лучше понимаешь сам и другим проще».
Сначала, задаваясь вопросом «как выразить это с помощью типов?», вы начнете писать качественный код в стиле TypeScript.
Вы можете следовать этой статье, используя Visual Studio Code на нашем YouTube-канале. Пожалуйста, также посмотрите наш YouTube-канал.