Как использовать TypeScript с React
В этой статье вы узнаете, как использовать TypeScript с React. К концу, у вас будет прочное понимание того, как писать код React с использованием TypeScript. Хотите посмотреть видео-версию этого руководства? Вы можете посмотреть видео ниже Содержание *
В этой статье вы узнаете, как использовать TypeScript с React.
К концу вы получите прочное понимание того, как писать код React с использованием TypeScript.
Хотите посмотреть видео-версию этого учебника? Вы можете посмотреть видео ниже:
Содержание
- Предварительные требования
- Начало работы
- Основы React и TypeScript
- Три способа определения типов свойств
- Как создать приложение со случайным списком пользователей
- Как сохранить список пользователей в состоянии
- Как отображать пользователей на интерфейсе
- Как создать отдельный компонент пользователя
- Как создать отдельный файл для объявления типов
- Как отображать индикатор загрузки
- Как загружать пользователей по нажатию кнопки
- Как обрабатывать событие изменения
- Спасибо за чтение
Предварительные требования
Для работы с этим учебником вам понадобится:
- базовое знание работы с React
- базовое понимание написания кода на TypeScript
Начало работы
Для начала работы с TypeScript вам нужно установить TypeScript на вашу машину. Вы можете сделать это, выполнив команду npm install -g typescript
в терминале или командной строке.
Теперь мы создадим проект Vite с использованием TypeScript.
npm create vite
После выполнения команды вам будет задано несколько вопросов.
Для имени проекта введите react-typescript-demo
.
Для фреймворка выберите React
, а для варианта выберите TypeScript
.
После создания проекта откройте его в VS Code и выполните следующие команды в терминале:
cd react-typescript-demonpm install
Теперь давайте проведем некоторую очистку кода.
Удалите файл src/App.css
и замените содержимое файла src/App.tsx
следующим содержимым:
const App = () => { return <div>App</div>;};export default App;
После сохранения файла вы можете увидеть красные подчеркивания в файле, как показано ниже:
Если вы получаете эту ошибку, просто нажмите Cmd + Shift + P(Mac)
или Ctrl + Shift + P(Windows/Linux)
для открытия панели команд VS Code и введите текст TypeScript
в поле поиска и выберите опцию TypeScript: Select TypeScript Version...
:
После выбора вы увидите варианты выбора между версией VS Code и версией рабочего пространства, как показано ниже:
Из этих вариантов вам необходимо выбрать вариант Use Workspace Version
. После выбора этого варианта ошибка из файла App.tsx
исчезнет.
Теперь откройте файл src/index.css
и замените его содержимое следующим кодом:
:root { font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; line-height: 1.5; font-weight: 400; font-synthesis: none; text-rendering: optimizeLegibility; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; -webkit-text-size-adjust: 100%;}
Теперь давайте запустим приложение, выполнив команду npm run dev
.
Теперь щелкните по отображаемому URL и получите доступ к приложению. Вы увидите следующий начальный экран с текстом App
, отображаемым в браузере.
Основы React и TypeScript
При использовании React с TypeScript первое, что вам следует знать, это расширение файла.
Каждому файлу React + TypeScript необходимо добавить расширение .tsx
.
Если файл не содержит никакого специфичного для JSX кода, то вместо расширения .tsx
можно использовать расширение .ts
.
Чтобы создать компонент в React с TypeScript, вы можете использовать тип FC
из пакета react
и использовать его после имени компонента.
Так что откройте файл src/App.tsx
и замените его следующим содержимым:
import { FC } from 'react';const App: FC = () => { return <div>App</div>;};export default App;
Теперь передадим некоторые свойства в этот компонент App
.
Откройте файл src/main.tsx
и передайте свойство title
компоненту App
следующим образом:
import React from 'react';import ReactDOM from 'react-dom/client';import App from './App.tsx';import './index.css';ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App title='TypeScript Demo' /> </React.StrictMode>);
Однако при добавлении свойства title
у нас теперь возникла ошибка TypeScript, как вы видите ниже:
Три способа определения типов свойств
Мы можем исправить вышеуказанную ошибку TypeScript тремя разными способами.
- Объявление типов с использованием интерфейса
Ошибка возникает из-за того, что мы добавили свойство title
как обязательное свойство для компонента App
– так что нам нужно указать это внутри компонента App
.
Откройте файл src/App.tsx
и замените его следующим кодом:
import { FC } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = () => { return <div>App</div>;};export default App;
Как вы можете видеть выше, мы добавили дополнительный интерфейс AppProps
для указания, какие свойства принимает компонент. Мы также использовали интерфейс AppProps
после FC
в угловых скобках.
Это хорошая практика и рекомендуется начинать имя интерфейса с заглавной буквы, например, AppProps
в нашем случае.
Теперь, после этого изменения, ошибка TypeScript исчезнет, как вы можете видеть ниже:
Вот как мы указываем, какие props принимает определенный компонент.
- Объявление типов с использованием ключевого слова
type
Мы также можем объявлять типы props с использованием ключевого слова type
.
Так что откройте файл App.tsx
и измените следующий код:
import { FC } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = () => { return <div>App</div>;};export default App;
на следующий код:
import { FC } from 'react';type AppProps = { title: string;};const App: FC<AppProps> = () => { return <div>App</div>;};export default App;
Здесь, вместо объявления interface
, мы использовали объявление type
. Теперь код будет работать без ошибок TypeScript.
Вам решать, какой вы будете использовать. Я всегда предпочитаю использовать интерфейс для объявления типов компонента.
- Использование встроенного объявления типов
Третий способ объявления типа – это определение встроенных типов, как показано ниже:
const App = ({ title }: { title: string }) => { return <div>App</div>;};export default App;
Как вы видите, мы удалили использование FC
, так как это не нужно, и при деструктуризации свойства title
мы определили его тип.
Итак, из этих трех способов вы можете использовать любой, который вам нравится. Я всегда предпочитаю использовать интерфейс с FC
. Таким образом, если я захочу позже добавить больше props, код не будет выглядеть сложным (что произойдет, если вы определите встроенные типы).
Теперь давайте использовать prop title
и отобразить его на UI.
Замените содержимое файла App.tsx
на следующий код:
import { FC } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { return <h1>{title}</h1>;};export default App;
Как вы видите, мы используем интерфейс с FC
, а также деструктурируем свойство title
и отображаем его на экране.
Теперь откройте файл src/index.css
и добавьте в него следующий CSS:
h1 { text-align: center;}
Если вы проверите приложение в браузере, вы увидите, что заголовок с текстом TypeScript Demo
правильно отображается.
Как создать приложение со списком случайных пользователей
Теперь, когда у вас есть базовое представление о том, как объявлять props компонентов, давайте создадим простое приложение со списком случайных пользователей, которое будет отображать список из 10 случайных пользователей на экране.
Для этого мы будем использовать API Random User Generator.
Вот URL API, который мы будем использовать:
https://randomuser.me/api/?results=10
Давайте сначала установим библиотеку Axios с помощью npm, чтобы мы могли сделать вызов API с ее помощью.
Выполните следующую команду, чтобы установить библиотеку Axios:
npm install axios
После установки перезапустите приложение, выполнив команду npm run dev
.
Теперь замените содержимое файла App.tsx
на следующий код:
import axios from 'axios';import { FC, useEffect } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { useEffect(() => { const getUsers = async () => { try { const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); } catch (error) { console.log(error); } }; getUsers(); }, []); return <h1>{title}</h1>;};export default App;
Как видите выше, мы добавили хук useEffect
, где выполняем API-запрос для получения списка пользователей.
Теперь, если вы откроете консоль в браузере, вы увидите отображение ответа API в консоли.
Как видите, мы успешно получаем список из 10 случайных пользователей, и фактический список пользователей находится в свойстве results
ответа.
Как сохранить список пользователей в состоянии
Теперь давайте сохранем этих пользователей в состоянии, чтобы мы могли отобразить их на экране.
Внутри компонента App
объявите новое состояние со значением пустого массива, вот так:
const [users, setUsers ] = useState([]);
И вызовите функцию setUsers
для сохранения пользователей в хуке useEffect
после вызова API.
Таким образом, ваш компонент App
будет выглядеть так:
import axios from 'axios';import { FC, useEffect, useState } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { const [users, setUsers] = useState([]); useEffect(() => { const getUsers = async () => { try { const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } }; getUsers(); }, []); return <h1>{title}</h1>;};export default App;
Как видите здесь, мы вызываем функцию setUsers
со значением data.results
.
Как отобразить пользователей на пользовательском интерфейсе
Теперь давайте отобразим имя и электронную почту каждого отдельного пользователя на экране.
Если вы проверите вывод в консоль, вы увидите, что у каждого объекта есть свойство name
, содержащее имя и фамилию пользователя. Мы можем объединить их, чтобы отобразить полное имя.
Также у каждого объекта пользователя есть прямое свойство email
, которое мы можем использовать для отображения электронной почты.
Замените содержимое файла App.tsx
следующим кодом:
import axios from 'axios';import { FC, useEffect, useState } from 'react';interface AppProps { title: string;}const App: FC<AppProps> = ({ title }) => { const [users, setUsers] = useState([]); useEffect(() => { const getUsers = async () => { try { const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } }; getUsers(); }, []); return ( <div> <h1>{title}</h1> <ul> {users.map(({ login, name, email }) => { return ( <li key={login.uuid}> <div> Имя: {name.first} {name.last} </div> <div>Электронная почта: {email}</div> <hr /> </li> ); })} </ul> </div> );};export default App;
Как видите, мы используем метод map массива для перебора массива users
, и мы используем деструктуризацию объекта для деструктуризации свойств login
, name
и email
индивидуальных объектов user
. Также мы отображаем имя и электронную почту пользователя в виде неупорядоченного списка.
Но вы увидите ошибки TypeScript в файле, как показано ниже:
Это происходит потому, что, как вы видите выше, по умолчанию TypeScript предполагает, что тип массива users
это never[]
– поэтому он не может определить, какие свойства содержит массив users
.
Это означает, что нам нужно указать все используемые свойства вместе с их типами.
Итак, теперь объявите новый интерфейс после интерфейса AppProps
таким образом:
interface Users { name: { first: string; last: string; }; login: { uuid: string; }; email: string;}
Здесь мы указываем, что каждый отдельный user
будет объектом с свойствами name
, login
и email
. Мы также указываем тип данных каждого свойства.
Как видно, у каждого объекта user
, поступающего из API, есть много других свойств, таких как phone
, location
и другие. Но нам нужно указать только те свойства, которые мы используем в коде.
Теперь измените объявление массива users
в useState
следующим образом:
const [users, setUsers] = useState([]);
на это:
const [users, setUsers] = useState<Users[]>([]);
Здесь мы указываем, что users
это массив объектов типа Users
, который мы объявили.
Теперь, если вы проверите файл App.tsx
, вы увидите, что там нет ошибок TypeScript.
И вы сможете увидеть список из 10 случайных пользователей, отображаемый на экране:
Как вы видели ранее, мы объявили интерфейс Users
следующим образом:
interface Users { name: { first: string; last: string; }; login: { uuid: string; }; email: string;}
Но когда у вас есть вложенные свойства, они записываются так:
interface Name { first: string; last: string;}interface Login { uuid: string;}interface Users { name: Name; login: Login; email: string;}
Преимущество объявления отдельных интерфейсов для каждого вложенного свойства заключается в том, что если вы хотите использовать ту же структуру в другом файле, вы можете просто экспортировать любой из вышеуказанных интерфейсов и использовать их повторно в других файлах (вместо повторного объявления того же интерфейса снова).
Итак, экспортируем все вышеуказанные интерфейсы как импорт по имени. Так код будет выглядеть так:
export interface Name { first: string; last: string;}export interface Login { uuid: string;}export interface Users { name: Name; login: Login; email: string;}
Как я уже сказал ранее, вы также можете использовать объявление типа здесь вместо использования интерфейса, так что это будет выглядеть так:
type Name = { first: string; last: string;};type Login = { uuid: string;};type Users = { name: Name; login: Login; email: string;};
Как создать отдельный компонент пользователя
Когда мы используем метод map
для отображения чего-либо на экране, обычно разделяют эту часть отображения в отдельный компонент. Это упростит тестирование и сделает ваш код компонента короче.
Создайте папку components
внутри папки src
и создайте файл User.tsx
внутри нее. Затем добавьте следующее содержимое в этот файл:
const User = ({ login, name, email }) => { return ( <li key={login.uuid}> <div> Имя: {name.first} {name.last} </div> <div>Email: {email}</div> <hr /> </li> );};export default User;
Если вы сохраните файл, вы увидите ошибки TypeScript снова.
Так что снова нам нужно указать, какие свойства будет получать компонент User
. Мы также должны указать тип данных для каждого из них.
Так что обновленный файл User.tsx
будет выглядеть так:
import { FC } from 'react';import { Login, Name } from '../App';interface UserProps { login: Login; name: Name; email: string;}const User: FC<UserProps> = ({ login, name, email }) => { return ( <li key={login.uuid}> <div> Имя: {name.first} {name.last} </div> <div>Email: {email}</div> <hr /> </li> );};export default User;
Как вы видите выше, мы объявили интерфейс UserProps
выше, и указали его для компонента User
с помощью FC
.
Также обратите внимание, что мы не указываем тип данных для свойств name
и login
. Вместо этого мы используем экспортируемые типы из файла App.tsx
:
import { Login, Name } from '../App';
Поэтому хорошо объявлять отдельные типы для каждого вложенного свойства, чтобы мы могли использовать их в других местах.
Теперь мы можем использовать этот компонент User
внутри файла App.tsx
.
Измените следующий код:
{users.map(({ login, name, email }) => { return ( <li key={login.uuid}> <div> Имя: {name.first} {name.last} </div> <div>Email: {email}</div> <hr /> </li> );})}
на этот код:
{users.map(({ login, name, email }) => { return <User key={login.uuid} name={name} email={email} />;})}
Как вы, возможно, знаете, при использовании метода map
для массива, мы должны указать key
для родительского элемента, который в нашем случае является User
. Поэтому мы добавили свойство key
при использовании компонента User
, как показано выше.
Это означает, что нам не нужен ключ внутри компонента User
, поэтому мы можем удалить ключ и проп login
из компонента User
.
Таким образом, обновленный компонент User
будет выглядеть так:
import { FC } from 'react';import { Name } from '../App';interface UserProps { name: Name; email: string;}const User: FC<UserProps> = ({ name, email }) => { return ( <li> <div> Имя: {name.first} {name.last} </div> <div>Email: {email}</div> <hr /> </li> );};export default User;
Как видите, мы удалили проп login
из интерфейса, а также деструктурировали его. Приложение все еще работает без проблем, как вы можете видеть ниже.
Как создать отдельный файл для объявления типов
Как вы можете видеть, файл App.tsx
стал довольно большим из-за объявлений интерфейса. Обычно используется отдельный файл только для объявления типов.
Поэтому создайте файл App.types.ts
внутри папки src
и переместите все объявления типов из компонента App
в файл App.types.ts
:
export interface AppProps { title: string;}export interface Name { first: string; last: string;}export interface Login { uuid: string;}export interface Users { name: Name; login: Login; email: string;}
Обратите внимание, что в приведенном выше коде мы также экспортируем компонент AppProps
.
Теперь обновите файл App.tsx
, чтобы использовать эти типы, как показано ниже:
import axios from 'axios';import { FC, useEffect, useState } from 'react';import { AppProps, Users } from './App.types';import User from './components/User';const App: FC<AppProps> = ({ title }) => { const [users, setUsers] = useState<Users[]>([]); // ...};export default App;
Как вы видите выше, мы импортируем AppProps
и Users
из файла App.types
:
import { AppProps, Users } from './App.types';
И ваш файл User.tsx
будет выглядеть следующим образом:
import { FC } from 'react';import { Name } from '../App.types';interface UserProps { name: Name; email: string;}const User: FC<UserProps> = ({ name, email }) => { return ( <li> <div> Имя: {name.first} {name.last} </div> <div>Email: {email}</div> <hr /> </li> );};export default User;
Как вы видите выше, мы импортируем Name
из файла App.types
.
import { Name } from '../App.types';
Как отобразить индикатор загрузки
Всегда полезно отображать индикатор загрузки во время выполнения API-запроса для отображения чего-либо.
Поэтому давайте добавим новое состояние isLoading
внутри компонента App
:
const [isLoading, setIsLoading] = useState(false);
Как видите, мы не указали тип данных при объявлении состояния:
const [isLoading, setIsLoading] = useState<boolean>(false);
Это происходит потому, что при присвоении любого начального значения (false
в нашем случае) TypeScript автоматически выводит тип данных, которые мы будем хранить – в нашем случае это boolean
.
Когда мы объявляем состояние users
, не было ясно, что мы будем хранить только по начальному значению пустого массива []
. Поэтому нам нужно было указать его тип так:
const [users, setUsers] = useState<Users[]>([]);
Теперь измените код useEffect
на следующий код:
useEffect(() => { const getUsers = async () => { try { setIsLoading(true); const { data } = await axios.get( 'https://randomuser.me/api/?results=10' ); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } finally { setIsLoading(false); } }; getUsers();}, []);
Здесь мы вызываем setIsLoading
со значением true
перед вызовом API. Внутри блока finally
мы возвращаем его обратно в значение false
.
Код, написанный внутри блока finally
, будет выполняться всегда, независимо от успеха или неудачи. Поэтому, независимо от того, успешен ли вызов API или нет, мы должны скрыть сообщение о загрузке, и для этого мы используем блок finally
.
Теперь мы можем использовать значение состояния isLoading
для отображения сообщения о загрузке на экране.
После тега h1
и перед тегом ul
добавьте следующий код:
{isLoading && <p>Loading...</p>}
Теперь, если вы проверите приложение, вы сможете увидеть сообщение о загрузке во время загрузки списка пользователей.
Таким образом, это лучший пользовательский опыт.
Но если вы внимательно посмотрите на отображаемых пользователей, вы увидите, что пользователи меняются после загрузки.
Так что сначала мы видим набор из 10 случайных пользователей, а затем сразу после этого мы видим другой набор случайных пользователей без перезагрузки страницы.
Это происходит потому, что мы используем React версии 18 (которую вы можете проверить в файле package.json
) и React.StrictMode
внутри файла src/main.tsx
.
И с версией 18 React, при использовании React.StrictMode
каждый хук useEffect
выполняется дважды, даже если не указано зависимостей.
Это происходит только в среде разработки и не в продакшне, когда вы развертываете приложение.
Из-за этого API-запрос выполняется дважды. Так как API случайных пользователей возвращает новый набор случайных пользователей при каждом вызове API, мы устанавливаем другой набор пользователей в массив users
с помощью вызова setUsers
внутри хука useEffect
.
Вот почему мы видим, что пользователи меняются без обновления страницы.
Если вы не хотите такого поведения во время разработки, вы можете удалить React.StrictMode
из файла main.tsx
.
Измените следующий код:
import React from 'react';import ReactDOM from 'react-dom/client';import App from './App.tsx';import './index.css';ReactDOM.createRoot(document.getElementById('root')!).render( <React.StrictMode> <App title='TypeScript Demo' /> </React.StrictMode>);
на этот код:
import ReactDOM from 'react-dom/client';import App from './App.tsx';import './index.css';ReactDOM.createRoot(document.getElementById('root')!).render( <App title='TypeScript Demo' />);
Теперь, если вы проверите приложение, вы увидите, что список пользователей не меняется после его загрузки.
Как загрузить пользователей по нажатию кнопки
Теперь, вместо вызова API при загрузке страницы, давайте добавим кнопку “показать пользователей” и будем делать вызов API при клике на эту кнопку.
Так что после тега h1
добавьте новую кнопку, как показано ниже:
<button onClick={handleClick}>Показать пользователей</button>
Теперь добавьте метод handleClick
внутри компонента App
и переместите весь код из функции getUsers
в метод handleClick
:
const handleClick = async () => { try { setIsLoading(true); const { data } = await axios.get('https://randomuser.me/api/?results=10'); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } finally { setIsLoading(false); }};
Теперь вы можете удалить или закомментировать хук useEffect
, так как он больше не нужен.
Ваш обновленный файл App.tsx
будет выглядеть так:
import axios from 'axios';import { FC, useState } from 'react';import { AppProps, Users } from './App.types';import User from './components/User';const App: FC = ({ title }) => { const [users, setUsers] = useState([]); const [isLoading, setIsLoading] = useState(false); const handleClick = async () => { try { setIsLoading(true); const { data } = await axios.get('https://randomuser.me/api/?results=10'); console.log(data); setUsers(data.results); } catch (error) { console.log(error); } finally { setIsLoading(false); } }; return ( {title}
{isLoading && Загрузка...
} {users.map(({ login, name, email }) => { return ; })}
);};export default App;
Теперь, если вы проверите приложение, вы увидите, что пользователи загружаются только при нажатии кнопки “показать пользователей”. Мы также видим сообщение о загрузке.
Как обрабатывать изменение событий
Теперь давайте добавим поле ввода. Когда мы что-то вводим в это поле, мы будем отображать введенный текст под этим полем ввода.
Добавьте поле ввода после кнопки, как показано ниже:
<input type='text' onChange={handleChange} />
И объявите новое состояние для хранения введенного значения, как показано ниже:
const [username, setUsername] = useState('');
Теперь добавьте метод handleChange
внутри компонента App
, как показано ниже:
const handleChange = (event) => { setUsername(event.target.value);};
Однако вы увидите, что мы получаем ошибку TypeScript для параметра event
.
С TypeScript нам всегда необходимо указывать тип каждого параметра функции.
Здесь TypeScript не может определить тип параметра event
.
Чтобы узнать тип параметра event
, мы можем изменить следующий код:
<input type='text' onChange={handleChange} />
на следующий код:
<input type='text' onChange={(event) => {}} />
В данном случае мы используем встроенную функцию, потому что если мы используем встроенную функцию, то правильный тип автоматически передается в параметр функции, поэтому нам не нужно его указывать.
Если навести курсор мыши на параметр event
, вы сможете увидеть точный тип события, который мы можем использовать в нашей функции handleChange
, как показано ниже:
Теперь вы можете изменить следующий код:
<input type='text' onChange={(event) => {}} />
на этот код:
<input type='text' onChange={handleChange} />
Теперь давайте отобразим значение переменной состояния username
ниже поля ввода:
<input type='text' onChange={handleChange} /><div>{username}</div>
Если вы проверите приложение сейчас, вы увидите введенный текст, отображаемый ниже поля ввода.
Спасибо за чтение
Вот и все для этого учебного пособия. Надеюсь, вы многое из него усвоили.
Хотите посмотреть видеоверсию этого учебника? Вы можете посмотреть это видео.
Вы можете найти полный исходный код для этого приложения в этом репозитории.
Если вы хотите овладеть JavaScript, ES6+, React и Node.js с помощью понятного контента, загляните на мой YouTube-канал. Не забудьте подписаться.
Хотите быть в курсе новых материалов по JavaScript, React и Node.js? Подпишитесь на мою страницу LinkedIn.
Leave a Reply