TypeScript - это язык со статической типизацией, соответствующий JavaScript. Статически типизированный код легче поддерживать и сложнее взломать.

В этой статье предполагается, что вы уже немного знакомы с React и немного с TypeScript. Если нет, не волнуйтесь, я вас прикрыл. Просмотрите ресурсы ниже и поиграйте с примерами, прежде чем приступить к этой статье:
Я не буду тратить ваше время зря и сразу перейду к делу.
Настройка проекта 👨💻️
В этой статье мы не обсуждаем, как настроить проект React с TypeScript с нуля. При запуске всегда полезно сохранять простоту. Я уже подготовил для вас шаблон GitHub, чтобы начать работу без каких-либо настроек. Вы можете решить форк репо или просто использовать шаблон, чтобы начать новый проект. Это репо уже идет с настройками prettier, eslint, TypeScript и React. Загрузился с CRA. Найдите здесь. Ознакомьтесь с официальным документом React, если вы хотите выполнить свою собственную настройку React + TypeScript.
Напоминание о типах TypeScript
Дженерики - это инструменты для создания повторно используемых программных компонентов. Это дает нам возможность не связывать детали с типами. Это позволяет нам повторно использовать код с разными типами, сохраняя при этом безопасность типов.
В TypeScript универсальные шаблоны похожи на заполнитель для типов, которые можно вставлять динамически. Пример прояснит ситуацию.
Мы реализуем небольшую функцию под названием SimpleStack для хранения списка некоторых элементов. Если мы реализуем его без дженериков, наше объявление SimpleArray сможет содержать только один тип значений.
type SimpleStackType = {
data: Array<string>;
push: (item: string) => number;
pop: () => string;
};
Приведенное выше объявление позволяет нам создавать только стопку строк. Если мы хотим создать стек чисел с помощью описанного выше подхода, нам нужно объявить новый тип, заменив строковый тип числовым. Теперь давайте посмотрим на общие примеры.
type SimpleStackType<T> = {
data: Array<T>;
push: (item: T) => number;
pop: () => T;
};
T в приведенном выше коде является заполнителем для типа. Мы можем передать что угодно в T, и SimpleStackType будет работать как тип T. Теперь пример использования:
let stringStack: SimpleStackType<string>; // this like the first example with strict type of string
// but we are not limitted.
let numberStack: SimpleStackType<number>;
type Person = {
name: string;
age: number;
};
let personStack: SimpleStackType<Person>;
Хорошо, если вы новичок в дженериках, возможно, вы уже спрашивали, не можем ли мы использовать тип любой. Чем он отличается от любого и в чем преимущество?
Позвольте мне снова проиллюстрировать этот ответ примером. Итак, стек типа любой
type AnyStack = {
data: Array<any>;
push: (item: any) => number;
pop: () => any:
};
Реализация AnyStack позволяет нам хранить что угодно в стеке, но не обеспечивает безопасность типов. Кроме того, мы не можем быть уверены в том, какие методы или свойства доступны для элементов данных.
let anyStack: AnyStack;
// initialized anyStack here
anyStack.data.map(item => {
console.log('Item is of type any');
console.log('We are not certain about the type, that means we can access any properties or methods on the item. But if we don' want to break it, we should check the everything before calling it on item.');
// We are accessing toLowerCase method on item. We can do it, typescript won't complain because item is of type any.
// But item can be of anything, what if item is of type nunowmber?
});
Теперь вы можете убедиться, что any на самом деле не ваш тип, потому что он не обеспечивает типобезопасности! Вот тут-то и пригодятся дженерики.
let stringStack: SimpleStackType<string>;
// initialized here
stringStack.map(item => {
console.log('Item is of type string here);
console.log('You can even access .toLowerCase here! Should not be a surprise. You already knew it, right?');
});
Вы, должно быть, уже заметили, что синтаксис Array ‹someType›, который мы используем, также является реализацией Generics, и вы видите мощь, безопасность и гибкость Generics. Время реагировать сейчас.
Строительные блоки приложения React, Компоненты
Компоненты - это строительные блоки любого приложения React. Поскольку эта статья посвящена функциональным компонентам React в TypeScript, мы будем обсуждать только функциональные компоненты и некоторые другие объявления типов.
Функциональные компоненты
Функциональный компонент React похож на обычную функцию, которая возвращает действительный JSX. Приведенная ниже функция вывода типа представляет собой совершенно допустимый компонент React.
function SomeComponent() {
return (
<div> Hello from a react component!</div>
);
}
Если вы хотите быть более явным, React предоставляет общий тип FC (или используйте FunctionComponent, оба они одинаковы). Вы можете передать тип, который будет типом опоры, которую функция будет принимать.
type Props = {
name: string;
};
const AnotherComponent: FC<Props> = ({name}) => <div>Hi {name}</div>;
Вы можете определить другие поля на реквизите, определив их в реквизите. Вы можете сделать их необязательными, поставив перед ними вопросительный знак.
type Props = {
name: string;
optionalProp?: number;
};
const OneMoreComponent: FC<Props> = ({name}) => <div>Hi {name}</div>;
Объявления других типов
Теперь мы знакомы с основными функциональными компонентами, но нам может потребоваться когда-нибудь взаимодействовать с компонентами класса, а также есть некоторые другие важные типы из React, которые стоит знать.
React.ReactNode можно использовать как тип возвращаемого значения для функции, возвращающей JSX. Используется для детей React. Вы можете определить функцию, которая возвращает ReactNode для ввода ваших свойств рендеринга. Один пример поясняет:
type Props = {
nameRenderer: (name: string) => React.ReactNode;
};
Синтаксис React.Component ‹PropType, StateType› используется для объявления компонентов класса.
React.ComponentType можно использовать, если вы заботитесь только о компоненте и свойствах, которые он принимает, и не заботитесь о том, является ли он функциональным компонентом или компонентом класса. Вот его объявление:
// This is a type from react, use FC for functional component and Component for class components.
type React.ComponentType<P = {}> = React.ComponentClass<P, any> | React.FunctionComponent<P>
Реагировать на хуки: версия TypeScript
Поскольку мы говорим о функциональной составляющей, это определенно неполно, если не говорить о хуках. Но это определенно не введение в хуки React.
useState
Тип может быть передан в useState точно так же, как вы передаете тип в универсальный. Если тип вашего состояния не меняется в процессе работы вашего приложения, вы можете просто передать начальное состояние в useState, и TypeScript определит тип.
const [counter, setCounter] = useState(0);
Здесь тип счетчика всегда числовой. Что делать, если вам нужен более сложный тип, например пользователь:
const [user, setUser] = useState({name: 'Binod', age: 1, ageUnit: 'month' });
В этом случае TypeScript также выведет типы. Но если ваши пользовательские данные устанавливаются динамически, скажем, после события входа в систему, а пользователь изначально имеет значение NULL, как бы вы с этим справились?
const [user, setUser] = useState(null);
Не совсем так, потому что TypeScript определит тип пользователя как null и будет жаловаться, когда вы попытаетесь установить для него значение другого типа. Очевидно, вы можете использовать любой тип, но вы хотите безопасности типов, не так ли?
Если вы немного думаете или уже поняли, мы можем использовать здесь тип Union. Да, сейчас мы будем использовать общий синтаксис.
type User = {
name: string,
age: number,
ageUnit: string, // or better 'month' | 'year' | 'day' | 'week'
};
const [user, setUser] = useState<User | null>(null);
Технически мы могли бы достичь чего-то похожего, но не полностью выполнив сценарий использования, с помощью ненулевого утверждения оператора.
Теперь нам также нужно знать тип setUser, возвращаемого useState, если мы его передаем. А передача setValue - это круто, потому что React гарантирует ссылки. Их тип:
React.Dispatch<React.SetStateAction<StateType>
useMemo, useCallback, useDispatch и useContext, useRef - все они похожи с точки зрения работы с типами. Все они принимают типы, соответствующие количеству принимаемых аргументов. Так что, чтобы быть СУХИМИ, мы их не обсуждаем. useCallback просто возвращает переданную вам функцию с неизмененным типом. Вы можете использовать общий синтаксис, если компилятор каким-то образом запутается. Я видел несколько случаев с useMemo, но еще не видел useCallback.
Пользовательский крючок
Давайте напишем нашу логику счетчика как настраиваемый хук:
function useCounter(initialValue: number) {
const [counter, setCounter] = useState(initialValue);
const increment = useCallback(() => setCounter(c => c + 1), []);
const decrement = useCallback(() => setCounter(c => c - 1), []);
return [counter, increment, decrement];
}
Но TypeScript плохо воспроизводит приведенный выше код. Предполагаемый тип возвращаемого значения для ловушки будет объединением типов элементов массива. Итак, TypeScript будет жаловаться, когда вы попытаетесь вызвать функцию увеличения или уменьшения. Потому что TypeScript видит это как:
type Increment: number | () => void | () => void;
И один из типов в союзе не вызывается. Вам нужно будет явно определить возвращаемый тип ловушки. Сделаем это для простого типа кортежа:
Type Counter = [number, () => void, () => void];
Используйте тип Counter как возвращаемый тип настраиваемого хука, и все готово. Если ваш хук возвращает несколько значений, например, более трех, вы должны вместо этого вернуть объект. Это поможет вам поддерживать код в течение длительного времени.
Этого должно быть достаточно, чтобы вы начали. При необходимости вы можете поискать дополнительную информацию.
Если вам понравилась статья и вы интересуетесь Front-end разработкой, в частности TypeScript, JavaScript и React, вы можете подписаться на меня. В основном я публикую одну статью в неделю.
У вас есть вопросы или отзывы? Пожалуйста! Я был бы на седьмом небе от счастья, чтобы ответить. 😊
Оставляя вас с игровой площадкой TypeScript React 👋 - https://www.typescriptlang.org/play?jsx=2&esModuleInterop=true&q=423#example/typescript-with-react