Шпаргалка по использованию React с TypeScript

Шпаргалка по использованию React с TypeScript

React и TypeScript стали неотъемлемой частью современной веб-разработки. Их сочетание позволяет создавать надежные и масштабируемые приложения, используя преимущества обеих технологий. Эта шпаргалка предоставит разработчикам исчерпывающее руководство по эффективному использованию React с TypeScript.

Что такое React?

React — это JavaScript-библиотека для создания пользовательских интерфейсов, разработанная Facebook. Она позволяет разработчикам создавать сложные UI, разбивая их на небольшие, повторно используемые компоненты.

Что такое TypeScript?

TypeScript — это язык программирования с открытым исходным кодом, разработанный Microsoft. Он является надмножеством JavaScript, добавляя статическую типизацию и другие функции, которые повышают качество и поддерживаемость кода.

Преимущества использования TypeScript с React

  • Улучшенная читаемость кода
  • Раннее обнаружение ошибок
  • Улучшенная поддержка IDE
  • Более простой рефакторинг
  • Лучшая документация кода

Настройка проекта React с TypeScript

Для начала работы с React и TypeScript необходимо настроить проект. Вот пошаговое руководство:

Создание нового проекта

Для создания нового проекта React с TypeScript можно использовать Create React App с шаблоном TypeScript:

npx create-react-app my-app --template typescript

Эта команда создаст новый проект React с уже настроенным TypeScript.

Настройка существующего проекта

Если у разработчика уже есть проект React, и он хочет добавить TypeScript, можно выполнить следующие шаги:

  1. Установить необходимые зависимости:
    npm install --save-dev typescript @types/react @types/react-dom
  2. Переименовать файлы с расширением .js в .tsx для компонентов и .ts для других файлов
  3. Создать файл tsconfig.json в корне проекта:
    { "compilerOptions": { "target": "es5", "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, "esModuleInterop": true, "allowSyntheticDefaultImports": true, "strict": true, "forceConsistentCasingInFileNames": true, "module": "esnext", "moduleResolution": "node", "resolveJsonModule": true, "isolatedModules": true, "noEmit": true, "jsx": "react-jsx" }, "include": ["src"] }

Основы TypeScript в React

Теперь, когда проект настроен, рассмотрим основные концепции использования TypeScript в React.

Типизация пропсов компонентов

Одно из главных преимуществ TypeScript — возможность типизировать пропсы компонентов:

interface GreetingProps { name: string; age?: number; } const Greeting: React.FC = ({ name, age }) => { return 

Hello, {name}! {age && `You are ${age} years old.`}

; };

В этом примере определен интерфейс GreetingProps, который описывает ожидаемые пропсы компонента. Свойство age помечено как опциональное с помощью знака вопроса.

Типизация состояния

При использовании хуков можно также типизировать состояние компонента:

interface User { name: string; age: number; } const UserProfile: React.FC = () => { const [user, setUser] = useState(null); // ... return ( {user ? ( 

Name: {user.name}

Age: {user.age}

) : (

Loading...

)} ); };

Здесь тип состояния user определен как User | null, что означает, что оно может быть либо объектом User, либо null.

Типизация событий

TypeScript позволяет точно определить типы событий в React:

const handleChange = (event: React.ChangeEvent) => { console.log(event.target.value); }; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); // обработка отправки формы }; 

В этих примерах точно определены типы событий для обработчиков изменения input и отправки формы.

Продвинутые техники TypeScript в React

После освоения основ, разработчики могут перейти к более продвинутым техникам использования TypeScript в React.

Обобщенные компоненты (Generics)

Обобщенные компоненты позволяют создавать более гибкие и переиспользуемые компоненты:

interface ListProps { items: T[]; renderItem: (item: T) => React.ReactNode; } function List({ items, renderItem }: ListProps) { return ( 
    {items.map((item, index) => (
  • {renderItem(item)}
  • ))}
); } // Использование {item}} />

Этот обобщенный компонент List может работать с массивами любого типа.

Условные типы

Условные типы позволяют создавать более сложные типы на основе условий:

type ButtonProps = { type: T; } & (T extends "submit" ? { onSubmit: () => void } : { onClick: () => void }); const Button = ({ type, ...props }: ButtonProps) => { return 

В этом примере тип кнопки определяет, какой обработчик событий она должна принимать.

Утилиты типов

TypeScript предоставляет ряд встроенных утилит для работы с типами:

  • Partial: Делает все свойства типа T опциональными
  • Required: Делает все свойства типа T обязательными
  • Pick: Создает тип, выбирая только указанные свойства K из T
  • Omit: Создает тип, исключая указанные свойства K из T

Пример использования:

interface User { id: number; name: string; email: string; } type UpdateUserPayload = Partial>; function updateUser(id: number, payload: UpdateUserPayload) { // Обновление пользователя } updateUser(1, { name: 'John Doe' }); // Валидно updateUser(1, { id: 2 }); // Ошибка: свойство 'id' не существует в типе 'UpdateUserPayload' 

Работа с состоянием в React и TypeScript

Управление состоянием — ключевой аспект разработки React-приложений. TypeScript добавляет дополнительный уровень типобезопасности при работе с состоянием.

useState с TypeScript

При использовании хука useState, TypeScript может автоматически вывести тип состояния, но иногда полезно явно указать тип:

interface User { name: string; age: number; } const [user, setUser] = useState(null); // Позже в коде setUser({ name: 'John', age: 30 }); 

Здесь тип состояния user определен как User | null, что позволяет установить начальное значение null и позже обновить его объектом User.

useReducer с TypeScript

При использовании useReducer, TypeScript помогает определить типы для состояния и действий:

type State = { count: number; }; type Action = | { type: 'increment' } | { type: 'decrement' } | { type: 'reset'; payload: number }; const initialState: State = { count: 0 }; function reducer(state: State, action: Action): State { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; case 'reset': return { count: action.payload }; default: return state; } } function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <> Count: {state.count}     ); } 

В этом примере TypeScript обеспечивает типобезопасность для состояния и действий, предотвращая ошибки при диспетчеризации действий.

Контекст с TypeScript

При работе с контекстом React, TypeScript помогает обеспечить правильную типизацию:

interface ThemeContextType { theme: 'light' | 'dark'; toggleTheme: () => void; } const ThemeContext = React.createContext(undefined); function ThemeProvider({ children }: { children: React.ReactNode }) { const [theme, setTheme] = useState<'light' | 'dark'>('light'); const toggleTheme = () => { setTheme(prev => prev === 'light' ? 'dark' : 'light'); }; return (  {children}  ); } function useTheme() { const context = useContext(ThemeContext); if (context === undefined) { throw new Error('useTheme must be used within a ThemeProvider'); } return context; } function ThemedButton() { const { theme, toggleTheme } = useTheme(); return (  ); } 

Здесь TypeScript помогает убедиться, что контекст используется правильно и содержит ожидаемые значения.

Работа с формами в React и TypeScript

Формы — важная часть многих веб-приложений. TypeScript может значительно улучшить процесс работы с формами в React.

Типизация элементов формы

При работе с элементами формы важно правильно типизировать события:

const handleInputChange = (event: React.ChangeEvent) => { const { name, value } = event.target; // Обработка изменения }; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); // Обработка отправки формы }; 

Эти типы обеспечивают правильный доступ к свойствам event.target.

Управляемые компоненты с TypeScript

При создании управляемых компонентов формы, TypeScript помогает обеспечить согласованность между состоянием и пропсами:

interface FormData { username: string; email: string; age: number; } const FormComponent: React.FC = () => { const [formData, setFormData] = useState({ username: '', email: '', age: 0 }); const handleChange = (event: React.ChangeEvent) => { const { name, value} = event.target;
setFormData(prevData => ({
...prevData,
[name]: name === 'age' ? parseInt(value) : value
}));
};

const handleSubmit = (event: React.FormEvent) => {
event.preventDefault();
console.log(formData);
};

return (
); };

В этом примере TypeScript обеспечивает типобезопасность для состояния формы и обработчиков событий.

Валидация форм с TypeScript

TypeScript может помочь в создании типобезопасной системы валидации форм:

interface FormData { username: string; email: string; age: number; } type FormErrors = { [K in keyof FormData]?: string; }; const validateForm = (data: FormData): FormErrors => { const errors: FormErrors = {}; if (data.username.length < 3) { errors.username = 'Username must be at least 3 characters long'; } if (!/^\S+@\S+\.\S+$/.test(data.email)) { errors.email = 'Invalid email address'; } if (data.age < 18) { errors.age = 'You must be at least 18 years old'; } return errors; }; const FormWithValidation: React.FC = () => { const [formData, setFormData] = useState({ username: '', email: '', age: 0 }); const [errors, setErrors] = useState({}); const handleChange = (event: React.ChangeEvent) => { const { name, value } = event.target; setFormData(prevData => ({ ...prevData, [name]: name === 'age' ? parseInt(value) : value })); }; const handleSubmit = (event: React.FormEvent) => { event.preventDefault(); const validationErrors = validateForm(formData); setErrors(validationErrors); if (Object.keys(validationErrors).length === 0) { console.log('Form is valid', formData); } }; return ( 
{errors.username &&

{errors.username}

} {errors.email &&

{errors.email}

} {errors.age &&

{errors.age}

}
); };

Этот подход обеспечивает типобезопасность для данных формы, ошибок и функции валидации.

Работа с API в React и TypeScript

При работе с API в React-приложениях TypeScript может значительно улучшить процесс обработки данных и управления состоянием.

Типизация ответов API

Определение типов для ответов API помогает обеспечить правильную обработку данных:

interface User { id: number; name: string; email: string; } const fetchUser = async (id: number): Promise => { const response = await fetch(`https://api.example.com/users/${id}`); if (!response.ok) { throw new Error('Failed to fetch user'); } return response.json(); }; const UserComponent: React.FC<{ userId: number }> = ({ userId }) => { const [user, setUser] = useState(null); const [error, setError] = useState(null); useEffect(() => { fetchUser(userId) .then(setUser) .catch(err => setError(err.message)); }, [userId]); if (error) return 
Error: {error}
; if (!user) return
Loading...
; return (

{user.name}

Email: {user.email}

); };

В этом примере тип User определяет структуру данных, возвращаемых API, что помогает предотвратить ошибки при использовании этих данных.

Обработка ошибок API

TypeScript может помочь в создании более надежной системы обработки ошибок:

interface ApiError { message: string; code: number; } const fetchData = async (url: string): Promise => { const response = await fetch(url); if (!response.ok) { const error: ApiError = await response.json(); throw new Error(`${error.message} (Code: ${error.code})`); } return response.json(); }; const DataComponent: React.FC<{ url: string }> = ({ url }) => { const [data, setData] = useState(null); const [error, setError] = useState(null); useEffect(() => { fetchData(url) .then(setData) .catch((err: Error) => setError(err.message)); }, [url]); if (error) return 
Error: {error}
; if (!data) return
Loading...
; return
{JSON.stringify(data)}
; };

Здесь TypeScript помогает убедиться, что ошибки API обрабатываются последовательно и правильно отображаются пользователю.

Использование generics для гибких API-функций

Generics позволяют создавать более гибкие функции для работы с API:

async function api(url: string, options?: RequestInit): Promise { const response = await fetch(url, options); if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return await response.json(); } interface Post { id: number; title: string; body: string; } const PostList: React.FC = () => { const [posts, setPosts] = useState([]); useEffect(() => { api('https://jsonplaceholder.typicode.com/posts') .then(setPosts) .catch(console.error); }, []); return ( 
    {posts.map(post => (
  • {post.title}
  • ))}
); };

Эта обобщенная api функция может использоваться для различных типов данных, обеспечивая типобезопасность для каждого вызова.

Оптимизация производительности в React с TypeScript

TypeScript может помочь в оптимизации производительности React-приложений, предоставляя инструменты для более эффективной работы с компонентами и данными.

Мемоизация компонентов

React.memo может быть использован с TypeScript для оптимизации повторного рендеринга компонентов:

interface Props { name: string; age: number; } const Person: React.FC = React.memo(({ name, age }) => ( 

Name: {name}

Age: {age}

)); // Использование const App: React.FC = () => { const [name, setName] = useState('John'); const [age, setAge] = useState(30); return (
); };

TypeScript обеспечивает правильную типизацию пропсов для мемоизированного компонента.

Оптимизация колбэков с useCallback

useCallback может быть использован с TypeScript для оптимизации колбэков:

interface Props { onClick: () => void; } const Button: React.FC = React.memo(({ onClick }) => (  )); const App: React.FC = () => { const [count, setCount] = useState(0); const handleClick = useCallback(() => { setCount(prevCount => prevCount + 1); }, []); return ( 

Count: {count}

); };

TypeScript помогает убедиться, что типы колбэков согласованы между родительским и дочерним компонентами.

Оптимизация вычислений с useMemo

useMemo может быть использован с TypeScript для оптимизации сложных вычислений:

interface Props { numbers: number[]; } const SumComponent: React.FC = ({ numbers }) => { const sum = useMemo(() => numbers.reduce((acc, num) => acc + num, 0), [numbers]); return 
Sum: {sum}
; }; const App: React.FC = () => { const [numbers, setNumbers] = useState([1, 2, 3, 4, 5]); const addNumber = () => { setNumbers(prevNumbers => [...prevNumbers, Math.floor(Math.random() * 100)]); }; return (
); };

TypeScript обеспечивает правильную типизацию входных данных и результата мемоизированного вычисления.

Тестирование React-компонентов с TypeScript

TypeScript может значительно улучшить процесс тестирования React-компонентов, обеспечивая дополнительную типобезопасность и улучшая обнаружение ошибок.

Настройка окружения для тестирования

Для тестирования React-компонентов с TypeScript обычно используются Jest и React Testing Library. Вот пример настройки:

// package.json { "scripts": { "test": "jest" }, "devDependencies": { "@testing-library/react": "^13.0.0", "@testing-library/jest-dom": "^5.16.4", "@types/jest": "^27.4.1", "jest": "^27.5.1", "ts-jest": "^27.1.4", "typescript": "^4.6.3" } } // jest.config.js module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', setupFilesAfterEnv: ['@testing-library/jest-dom/extend-expect'], }; 

Написание тестов для компонентов

Вот пример теста для простого компонента с использованием TypeScript:

// Button.tsx interface ButtonProps { onClick: () => void; children: React.ReactNode; } const Button: React.FC = ({ onClick, children }) => (  ); // Button.test.tsx import React from 'react'; import { render, fireEvent } from '@testing-library/react'; import Button from './Button'; test('Button calls onClick prop when clicked', () => { const handleClick = jest.fn(); const { getByText } = render(); fireEvent.click(getByText('Click me')); expect(handleClick).toHaveBeenCalledTimes(1); }); 

TypeScript помогает убедиться, что пропсы компонента используются правильно в тесте.

Моки и стабы с TypeScript

TypeScript может помочь в создании типобезопасных моков и стабов для тестирования:

// api.ts export interface User { id: number; name: string; } export const fetchUser = async (id: number): Promise => { const response = await fetch(`https://api.example.com/users/${id}`); if (!response.ok) { throw new Error('Failed to fetch user'); } return response.json(); }; // UserComponent.tsx import React, { useState, useEffect } from 'react'; import { User, fetchUser } from './api'; interface Props { userId: number; } const UserComponent: React.FC = ({ userId }) => { const [user, setUser] = useState(null); useEffect(() => { fetchUser(userId).then(setUser); }, [userId]); if (!user) return 
Loading...
; return
{user.name}
; }; // UserComponent.test.tsx import React from 'react'; import { render, waitFor } from '@testing-library/react'; import UserComponent from './UserComponent'; import { fetchUser } from './api'; jest.mock('./api'); const mockFetchUser = fetchUser as jest.MockedFunction; test('UserComponent displays user name after fetching', async () => { mockFetchUser.mockResolvedValue({ id: 1, name: 'John Doe' }); const { getByText } = render(); expect(getByText('Loading...')).toBeInTheDocument(); await waitFor(() => { expect(getByText('John Doe')).toBeInTheDocument(); }); expect(mockFetchUser).toHaveBeenCalledWith(1); });

В этом примере TypeScript обеспечивает типобезопасность для мока функции fetchUser и помогает убедиться, что тест правильно взаимодействует с компонентом и API.

Работа с сторонними библиотеками в React и TypeScript

При использовании сторонних библиотек в React-проекте с TypeScript могут возникнуть некоторые сложности, но есть способы эффективно интегрировать эти библиотеки.

Использование @types пакетов

Многие популярные библиотеки имеют соответствующие @types пакеты, которые предоставляют определения типов:

npm install lodash npm install --save-dev @types/lodash 

После установки @types пакета, TypeScript автоматически распознает типы для библиотеки:

import _ from 'lodash'; const numbers = [1, 2, 3, 4, 5]; const sum = _.sum(numbers); // TypeScript знает, что sum будет числом 

Создание собственных определений типов

Если для библиотеки нет @types пакета, можно создать собственные определения типов:

// my-library.d.ts declare module 'my-library' { export function doSomething(value: string): number; export class MyClass { constructor(value: string); getValue(): string; } } // использование import { doSomething, MyClass } from 'my-library'; const result = doSomething('test'); // TypeScript знает, что result будет числом const instance = new MyClass('test'); const value = instance.getValue(); // TypeScript знает, что value будет строкой 

Использование сторонних компонентов

При использовании компонентов из сторонних библиотек, важно правильно типизировать их пропсы:

import React from 'react'; import { Button } from 'some-ui-library'; // Предполагаем, что библиотека не предоставляет типы interface ButtonProps { onClick: () => void; label: string; disabled?: boolean; } const MyComponent: React.FC = () => { const handleClick = () => { console.log('Button clicked'); }; return ( 

Если библиотека предоставляет свои собственные типы, их можно импортировать и использовать:

import React from 'react'; import { Button, ButtonProps } from 'some-ui-library'; const MyComponent: React.FC = () => { const handleClick: ButtonProps['onClick'] = () => { console.log('Button clicked'); }; return ( 

Продвинутые паттерны в React с TypeScript

TypeScript позволяет реализовать некоторые продвинутые паттерны в React, которые могут улучшить структуру и типобезопасность кода.

Компоненты высшего порядка (HOC)

HOC можно типизировать, используя обобщенные типы:

import React from 'react'; interface WithLoadingProps { loading: boolean; } function withLoading

( WrappedComponent: React.ComponentType

): React.FC

{ return ({ loading, ...props }: WithLoadingProps & P) => { if (loading) return

Loading...
; return ; }; } interface UserProps { name: string; } const UserComponent: React.FC = ({ name }) =>
{name}
; const UserWithLoading = withLoading(UserComponent); // Использование const App: React.FC = () => ( );

Рендер-пропсы

Паттерн рендер-пропсов также может быть типизирован с использованием TypeScript:

import React, { useState } from 'react'; interface ToggleProps { children: (props: { on: boolean; toggle: () => void }) => React.ReactNode; } const Toggle: React.FC = ({ children }) => { const [on, setOn] = useState(false); const toggle = () => setOn(!on); return <>{children({ on, toggle })}; }; // Использование const App: React.FC = () => (  {({ on, toggle }) => ( 
The button is {on ? 'on' : 'off'}
)}
);

Условный рендеринг

TypeScript может помочь в создании типобезопасных условных компонентов:

import React from 'react'; type Props = { condition: boolean; trueComponent: React.ComponentType; falseComponent: React.ComponentType; } & T; function ConditionalRender({ condition, trueComponent: TrueComponent, falseComponent: FalseComponent, ...rest }: Props) { return condition ?  : ; } // Использование const TrueComponent: React.FC<{ message: string }> = ({ message }) => 
{message}
; const FalseComponent: React.FC<{ message: string }> = ({ message }) => {message}; const App: React.FC = () => ( );

Оптимизация сборки React-приложения с TypeScript

Оптимизация сборки React-приложения с TypeScript может значительно улучшить производительность и время загрузки. Вот несколько стратегий для оптимизации:

Использование производственной сборки

При сборке для production, убедитесь, что используете флаг —production:

npm run build -- --production

Это активирует различные оптимизации, включая минификацию кода и удаление отладочного кода.

Оптимизация импортов

Используйте точечный импорт вместо импорта всего модуля, особенно для больших библиотек:

// Плохо import _ from 'lodash'; // Хорошо import isEqual from 'lodash/isEqual'; 

Это позволяет включать в сборку только необходимый код.

Разделение кода (Code Splitting)

Используйте динамический импорт для разделения кода:

import React, { lazy, Suspense } from 'react'; const LazyComponent = lazy(() => import('./LazyComponent')); const App: React.FC = () => ( Loading...
}> );

Это позволяет загружать компоненты по мере необходимости, уменьшая начальный размер бандла.

Оптимизация TypeScript-компилятора

Настройте tsconfig.json для оптимизации сборки:

{ "compilerOptions": { "target": "es5", "module": "esnext", "moduleResolution": "node", "importHelpers": true, "noEmitOnError": true, "strict": true, "jsx": "react" } } 

Эти настройки помогут оптимизировать процесс компиляции и уменьшить размер выходного кода.

Заключение

Использование TypeScript с React предоставляет множество преимуществ, включая улучшенную типобезопасность, более надежный код и улучшенный опыт разработки. В этой шпаргалке были рассмотрены ключевые аспекты работы с React и TypeScript, от базовых концепций до продвинутых техник и оптимизаций.

Разработчики, освоившие эти техники, смогут создавать более надежные и масштабируемые React-приложения. Однако важно помнить, что TypeScript — это инструмент, и как любой инструмент, он наиболее эффективен, когда используется правильно. Рекомендуется постоянно изучать новые возможности и лучшие практики, чтобы максимально эффективно использовать сочетание React и TypeScript в своих проектах.

Дополнительные ресурсы

Для дальнейшего изучения React с TypeScript рекомендуется обратиться к следующим ресурсам:

  • Официальная документация TypeScript
  • Официальная документация React
  • TypeScript Deep Dive — онлайн-книга о TypeScript
  • React+TypeScript Cheatsheets на GitHub
  • Блоги и статьи на Medium и Dev.to о React и TypeScript

Постоянная практика и изучение новых концепций помогут разработчикам стать экспертами в использовании React с TypeScript и создавать высококачественные, типобезопасные приложения.

Читайте также  Внедрение зависимостей в React
Советы по созданию сайтов