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

Используйте расширение LocatorJS для быстрого перехода к исходному коду в IDE (VS Code), просто щелкнув на любом из UI-элементов, отображаемых в браузере.
Это расширение выручит при работе над большим приложением, позволив легко найти файл, содержащий код отображаемого UI.
Оно также будет полезно при необходимости отладить код, написанный кем-то другим.
Используйте 𝗢𝗽𝘁𝗶𝗼𝗻/𝗔𝗹𝘁 + 𝗖𝗹𝗶𝗰𝗸 для перехода к исходному коду.
Это расширение станет спасением при работе с кодом библиотеки Material UI, написанным другими разработчиками, позволив найти точный код для любой части UI.
2. Избегайте жестко заданного в коде URL API
Выполняя вызов API, всегда избегайте жестко заданного в коде URL API. Вместо этого создайте src/utils/constants.js
или аналогичный файл и добавьте в него URL API следующим образом:
export const BASE_API_URL = "https://jsonplaceholder.typicode.com";
Где бы ни понадобилось выполнить вызов API, используйте эту константу, применяя синтаксис шаблонного литерала, как здесь:
// posts.jsx
import { BASE_API_URL } from '../utils/constants.js';
const {data} = await axios.get(`${BASE_API_URL}/posts`);
// users.jsx
import { BASE_API_URL } from '../utils/constants.js';
const {data} = await axios.get(`${BASE_API_URL}/users`);
Тогда, если позже потребуется изменить значение BASE_API_URL
, не нужно будет менять его в нескольких файлах.
Достаточно обновить его в файле constants.js
, и оно будет отражено везде.
P. S. Можете даже хранить значение BASE_API_URL
в файле .env
, а не в файле constants.js
, чтобы легко изменить его в зависимости от среды разработки или продакшна.
3. Упорядочивайте React-компоненты с помощью Barrel-экспорта
Работа над большим React-проектом обычно требует создания нескольких папок, содержащих различные компоненты.
В таких случаях файл, в котором используются различные компоненты, будет содержать множество операторов импорта (см. пример ниже):
import ConfirmModal from './components/ConfirmModal/ConfirmModal';
import DatePicker from './components/DatePicker/DatePicker';
import Tooltip from './components/Tooltip/Tooltip';
import Button from './components/Button/Button';
import Avatar from './components/Avatar/Avatar';
Это выглядит не очень хорошо, поскольку с увеличением количества компонентов увеличивается и количество операторов импорта.
Чтобы решить данную проблему, создайте в родительской папке (components
) файл index.js
и экспортируйте из него все компоненты в виде именованного экспорта следующим образом:
export { default as ConfirmModal } from './ConfirmModal/ConfirmModal';
export { default as DatePicker } from './DatePicker/DatePicker';
export { default as Tooltip } from './Tooltip/Tooltip';
export { default as Button } from './Button/Button';
export { default as Avatar } from './Avatar/Avatar';
Это нужно сделать только один раз. Теперь, когда в одном из файлов понадобится доступ к какому-либо компоненту, можно легко импортировать его с помощью именованного импорта в одной строке, как показано ниже:
import {ConfirmModal, DatePicker, Tooltip, Button, Avatar} from './components';
Это аналогично следующему:
import {ConfirmModal, DatePicker, Tooltip, Button, Avatar} from './components/index';
Это стандартная практика при работе над крупными проектами в промышленности/компании.
Она известна как паттерн «Barrel», который позволяет так организовать файлы, чтобы экспортировать все модули в каталоге через один файл.
Демонстрация CodeSandbox предоставляет возможность увидеть этот паттерн в действии.
4. Храните приватный API-ключ на стороне бэкенда
Использование .env
-файла для хранения приватной информации вроде API_KEY
в React-приложении не гарантирует приватность этой информации.
React-приложение работает на стороне клиента/браузера, поэтому любой может увидеть его, если API-ключ используется как часть API URL
из вкладки «Сеть» (Network tab).
Лучший способ сделать его приватным — хранить на стороне бэкенда.
Однако учтите: хотя использование .env
-файла внутри React не гарантирует абсолютной конфиденциальности, рекомендуется следовать этой практике для дополнительного уровня безопасности.
Таким образом, никто не сможет узнать API_KEY
непосредственно в исходном коде приложения.
Кроме того, по соображениям безопасности не выкладывайте файл .env
на GitHub. Вместо этого добавьте его в файл .gitignore
.
5. Помните: передача компоненту другого ключа приводит к пересозданию компонента
<BookForm
key={book.id}
book={book}
handleSubmit={handleSubmit}
/>
Компонент, которому передан другой ключ, будет создан заново со сбросом всех его данных и состояния.
Это может быть полезно, если нет необходимости сохранять предыдущую информацию или нужно сбросить состояние компонента после какого-либо действия.
Например, при открытии на одной странице разных модалов для отображения различной информации можно передать уникальный ключ для каждого модала. Тогда при каждом открытии модала предыдущая информация не будет сохраняться, что позволит отображать корректные данные соответствующего модала.
Это одна из причин, по которой при использовании метода array map нужен уникальный ключ, который не будет меняться при повторном рендеринге.
Дело в том, что при изменении ключа React пересоздает элемент и все его дочерние элементы, включая компоненты, вложенные в родительский элемент. Это приводит к серьезным проблемам с производительностью.
6. Организуйте компоненты, создав для каждого из них отдельную папку
При создании какого-либо компонента не помещайте его непосредственно в папку components
.
Вместо этого создайте отдельные папки для отдельных компонентов внутри папки components
, как показано ниже:
Copy--- components
--- header
--- Header.jsx
--- Header.css
--- Header.test.js
--- footer
--- Footer.jsx
--- Footer.css
--- Footer.test.js
--- sidebar
--- Sidebar.jsx
--- Sidebar.css
--- Sidebar.test.js
Как видно из приведенного выше кода, папки header
, footer
и sidebar
созданы внутри папки components
, а внутри папки header
хранятся все файлы, относящиеся к заголовку (header
).
То же самое относится к папкам footer
и sidebar
.
7. Не используйте Redux для каждого React-приложения
Не используйте Redux изначально при создании React-приложения.
Для небольших приложений достаточно использовать React Context API или хук useReducer для управления React-приложением.
Ниже перечислены случаи, позволяющие выбрать Redux.
- Для управления глобальным состоянием: когда нужно поддержать глобальное состояние приложения, например при аутентификации, сборе информации о профиле или данных, которые должны быть общими для нескольких компонентов.
- Для обмена данными между несвязанными компонентами: когда в приложении есть несколько страниц, использующих маршрутизацию, и поэтому невозможно поднять состояние до родительского компонента.
- Для последовательного обновления состояния: Redux обеспечивает единый источник истины для состояния приложения, что упрощает последовательное обновление и поддержку состояния приложения.
- Для масштабируемости: Redux помогает приложению масштабироваться, предоставляя четкий шаблон для управления состоянием по мере роста размера и сложности приложения.
8. Используйте метод console.count для определения количества повторных рендерингов компонента

Нередко бывает полезно знать, сколько раз выполняется определенная строка кода.
Иногда это необходимо для того, чтобы понять, сколько раз выполняется определенная функция.
В таком случае можно использовать метод console.count, передав ему в качестве аргумента уникальную строку.
Допустим, у вас есть React-код и нужно узнать, сколько раз компонент рендерится повторно. Вместо того, чтобы добавлять console.log
и вручную считать, сколько раз он выводится в консоль, добавьте в компонент console.count('render')
.
Вы увидите сообщение о рендеринге вместе с подсчетом того, сколько раз он был выполнен.
9. Избегайте объявления нового состояния для хранения всего внутри компонента
Прежде чем объявлять новое состояние в компоненте, всегда дважды подумайте, действительно ли нужно хранить эту информацию в состоянии.
Ведь объявление переменной состояния потребует сразу же вызвать setState, чтобы обновить значение состояния. И при каждом изменении состояния React будет повторно рендерить компонент, содержащий это состояние, а также все его прямые и косвенные дочерние компоненты.
Таким образом, введение одного состояния добавляет дополнительный повторный рендеринг в приложение. При наличии нескольких «тяжелых» дочерних компонентов, выполняющих большой объем фильтрации, сортировки или манипулирования данными, обновление UI с новыми изменениями займет больше времени.
Если нужно просто отследить какое-то значение, не передаваемое ни одному дочернему компоненту и не применяемое при рендеринге, используйте хук useRef для хранения этой информации.
Обновление значения ref не приведет к повторному рендерингу компонента.
10. Не объявляйте каждую функцию внутри компонента
Функцию, которая не зависит от состояния или свойств компонента, рекомендуется объявлять не внутри, а вне компонента.
Дело в том, что при каждом рендеринге компонента все функции функционального компонента создаются заново. При наличии множества функций в компоненте это может замедлить работу приложения.
Взгляните на приведенный ниже код:
const getShortDescription = (description) => {
return description.slice(0, 20);
}
Приведенная выше функция не имеет зависимости от состояния или значения свойства.
Мы только передаем значение для получения сокращенного текста. Поэтому нет необходимости объявлять функцию внутри компонента.
Кроме того, если в разных компонентах требуется одна и та же функция с кратким описанием, то вместо того, чтобы повторять код в этих компонентах, перенесите функцию в другой файл, например utils/functions.js
, экспортируйте ее оттуда и используйте в нужных компонентах.
Так компоненты станут более понятными.
11. Обновите React-приложения как минимум до 18-й версии
Версия React 18 повышает производительность приложений по сравнению с предыдущими версиями.
В 18-й версии React объединяет несколько обновлений состояния в один цикл рендеринга. Это полезно, если у вас есть несколько вызовов setState в такой функции, как приведенная ниже:
const handleUpdate = (user) => {
setCount(count => count + 1);
setIsOpen(open => !open);
setUser(user);
}
React не будет повторно рендерить компонент три раза, просто потому, что есть три вызова обновления состояния. Вместо этого будет только один рендеринг.
В React до 18-й версии объединяются только обновления состояния внутри функций-обработчиков событий. Однако обновления состояния внутри промисов, функции setTimeout
, нативных обработчиков событий или любых других обработчиков событий не собираются вместе.
При использовании React до 18-й версии в приведенном выше коде, поскольку есть три обновления состояния, компонент будет отрисован три раза. Это неэффективно с точки зрения производительности.
В версии 18-й обновления состояния внутри тайм-аутов, промисов, нативных обработчиков событий или любых других событий собираются в пакет. Поэтому в приведенном выше коде рендеринг происходит только один раз.
12. Всегда создавайте экземпляр QueryClient вне, а не внутри компонента
При использовании Tanstack Query/React Query никогда не создавайте экземпляр QueryClient
внутри компонента.
Посмотрим, что будет при создании нового клиента внутри компонента (учитывая, что QueryClient
хранит кэш запросов):
const queryClient = new QueryClient();
При каждом повторном рендеринге компонента получим новую копию кэша запросов. Таким образом, данные запросов не будут кэшироваться, и функция кэширования не будет работать ожидаемо.
Поэтому всегда создавайте экземпляр QueryClient
вне компонента, а не внутри него.
// Вместо того, чтобы писать код так:
const App = () => {
// ❌ Не создавайте queryClient внутри компонента. Это неправильно.
const queryClient = new QueryClient();
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
}
// пишите так:
// ✅ Это правильное место для создания queryClient.
const queryClient = new QueryClient();
const App = () => {
return (
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
);
}
13. Используйте расширение ReacTree VSCode для выяснения отношений «предок-потомок»
Использование расширения ReacTree VS Code позволяет быстро увидеть древовидное представление компонентов React-приложения.
Это отличный способ выяснить отношения между родительскими и дочерними компонентами.

14. Не пренебрегайте библиотеками вроде react-hook-form при работе с формами
При работе как с простыми, так и со сложными формами в React я всегда предпочитаю react-hook-form — самую популярную React-библиотеку, связанную с формами.
Она использует refs вместо state для управления входными данными, что помогает избежать проблем с производительностью в приложении.
С этой библиотекой компонент не будет рендерится повторно при каждом введенном символе, что происходит, когда вы сами управляете им с помощью состояния.
Более того, вы получаете дополнительное преимущество в виде простого способа реализации реактивных валидаций для форм.
Кроме этого, код компонента будет намного проще и понятнее.
Библиотека react-hook-form настолько эффективна, что даже библиотека shdacn/ui использует ее для своего компонента Form.
15. Избегайте передачи функции setState в качестве свойства дочерним компонентам
Никогда не передавайте функцию setState непосредственно в качестве свойства дочерним компонентам, как в этом случае:
const Parent = () => {
const [state, setState] = useState({
name: '',
age: ''
})
.
.
.
return (
<Child setState={setState} />
)
}
Состояние компонента может быть изменено только самим компонентом.
Вот почему:
- Это гарантирует предсказуемость кода. Если передать
setState
напрямую нескольким компонентам, будет сложно определить, откуда происходит изменение состояния.
- Отсутствие предсказуемости может привести к неожиданному поведению и затруднить отладку кода.
- По мере роста приложения может потребоваться рефакторинг или изменение управления состоянием в родительском компоненте.
- Если дочерние компоненты полагаются на прямой доступ к
setState
, эти изменения могут распространиться по всей кодовой базе и потребовать обновлений в нескольких местах, что увеличивает риск появления ошибок.
- Если в состояние входят конфиденциальные данные, то прямая передача
setState
может потенциально раскрыть эти данные дочерним компонентам, что повышает риски безопасности.
- Алгоритм согласования React-компонентов работает более эффективно, когда обновления состояния и свойств четко определены в рамках компонентов.
Вместо того, чтобы передавать setState
напрямую, рекомендуется:
- Передавать данные в качестве свойств. Передавайте данные, которые нужны дочернему компоненту, как свойства, а не саму функцию
setState
. Таким образом, вы предоставите дочернему компоненту понятный интерфейс для получения данных, не раскрывая деталей реализации состояния.
- Передавать функцию как свойство. Если дочернему компоненту необходимо взаимодействовать с состоянием родительского компонента, можно передать функцию в качестве свойства. Объявив функцию в родительском компоненте и обновив состояние в этой функции, можете передать эту функцию в качестве свойства дочернему компоненту и вызывать ее из дочернего компонента, когда это необходимо.
16. Избегайте использования вложенных тернарных операторов
При использовании тернарного оператора никогда не выходите за рамки одного условия и избегайте написания вложенных условий.
// Хорошо:
{isAdmin ? <Dashboard /> : <Profile /> }
// Плохо:
{isAdmin ? <Dashboard /> : isSuperAdmin ? <Settings /> : <Profile />}
Создание вложенных условий при применении тернарного оператора усложняет код для чтения и сопровождения даже при использовании форматировщика кода.
Чтобы проверить более одного условия, используйте if/else, switch case или lookup map, но никогда так не применяйте тернарный оператор:
if (isAdmin) {
return <Dashboard />;
} else if (isSuperAdmin) {
return <Settings />;
} else {
return <Profile />;
}
// ИЛИ
switch (true) {
case isAdmin:
return <Dashboard />;
case isSuperAdmin:
return <Settings />;
default:
return <Profile />;
}
// ИЛИ
{isAdmin && <Dashboard />}
{isSuperAdmin && <Settings />}
{!isAdmin && !isSuperAdmin && <Profile />}
// можете использовать lookup map таким образом:
const data = {
isAdmin: <Dashboard />,
isSuperAdmin: Settings />,
Neither: <Profile />
}
// и получайте доступ к данным с помощью data['isAdmin'],
// в результате чего отобразится страница Dashboard.
Это относится не только к React, но и к JavaScript и другим языкам программирования.
17. Не создавайте отдельный файл для каждого компонента
Не обязательно создавать каждый компонент в отдельном файле.
Если компонент используется только в определенном файле и просто возвращает минимальный JSX-код, создайте этот компонент в том же файле.
При этом отпадает необходимость ненужного создания дополнительных файлов. Кроме того, легче понять код, который находится в одном файле.
const ProfileDetailsHeader = () => {
return (
<header>
<h1 className='text-3xl font-bold leading-10'>
Profile Details
</h1>
<p className='mt-2 text-base leading-6 text-neutral-500'>
Добавьте свои данные, чтобы придать вашему профилю индивидуальность.
</p>
</header>
);
};
const Profile = () => {
return (
<ProfileDetailsHeader />
...
<ProfileDetailsFooter />
);
}
18. Никогда не храните приватную информацию непосредственно в коде
Многие разработчики напрямую записывают в код приватную информацию, например конфигурацию Firebase, вот так:
const config = {
apiKey: 'AIdfSyCrjkjsdscbbW-pfOwebgYCyGvu_2kyFkNu_-jyg',
projectId: 'seventh-capsule-78932',
...
};
Никогда не делайте этого, если не хотите иметь серьезных проблем с безопасностью. Отправляя файл с такой конфигурацией на GitHub, вы предоставляете прямой доступ к данным Firebase тем, кто клонирует репозиторий. Они могут добавлять, редактировать или удалять данные из Firebase.
Чтобы избежать этой проблемы, создайте в проекте файл с именем .env
, а для каждого свойства объекта config — переменную среды, как показано ниже:
// Если используете Vite.js
VITE_APP_API_KEY=AIdfSyCrjkjsdscbbW-pfOwebgYCyGvu_2kyFkNu_-jyg
// получите доступ в виде import.meta.env.VITE_APP_API_KEY
// Если используете create-react-app
REACT_APP_API_KEY=AIdfSyCrjkjsdscbbW-pfOwebgYCyGvu_2kyFkNu_-jyg
// и получите доступ в виде process.env.REACT_APP_API_KEY
Кроме того, добавьте запись, сделанную в .env
-файле, в .gitignore
, чтобы она не была отправлена на GitHub.
19. Всегда переносите бизнес-логику компонента в пользовательские хуки
При любой возможности старайтесь максимально использовать пользовательские хуки.
Всегда помещайте всю бизнес-логику компонента и вызовы API в пользовательские хуки.
Так код компонента будет выглядеть чистым и легким для понимания, поддержки и тестирования.
const ResetPassword = () => {
const { isPending, sendResetEmail, error } = useResetPassword();
// JSX
}
20. Учтите, что каждое применение пользовательского хука создает новый экземпляр
Большинство неопытных React-разработчиков допускают эту ошибку, когда только начинают разбираться в пользовательских хуках.
Используя определенный пользовательский хук в нескольких компонентах, вы можете полагать, что каждый компонент будет обращаться к одним и тем же данным, возвращаемым этим хуком, вот так:
const [show, toggle] = useToggle();
Однако это не так.
Каждый компонент, использующий этот хук, будет иметь отдельный экземпляр хука.
В результате все состояния, обработчики событий и другие данные, объявленные в этом пользовательском хуке, будут разными для каждого компонента, использующего этот хук.
Поэтому, если вам нужно использовать одни и те же данные и обработчики событий для всех компонентов, импортируйте пользовательский хук только в одном месте в родительском компоненте всех этих компонентов.
Затем можете передать данные, возвращаемые пользовательским хуком, в качестве свойства или использовать Context API для доступа к ним из определенных компонентов.
Никогда не совершайте ошибку, используя один и тот же пользовательский хук в разных компонентах, полагая, что изменение данных хука в одном компоненте автоматически обновит их в другом.
Демонстрация СodeSandbox позволит убедиться в этом.
21. Избегайте потери свойств состояния при обновлении объектов с помощью React-хуков
При использовании компонентов класса, если приходится иметь дело с состоянием с несколькими свойствами, как здесь:
state = {
name: '',
isEmployed: false
};
Для обновления состояния указывается только то свойство, которое требуется обновить, как здесь:
this.setState({
name: 'David'
});
// ИЛИ
this.setState({
isEmployed: true
});
Другие свойства состояния не будут потеряны при обновлении какого-либо свойства.
Но при работе с React-хуками, если состояние хранит объект, как здесь:
const [state, setState] = useState({
name: '',
isEmployed: false
});
нужно вручную расширить предыдущие свойства при обновлении определенного свойства состояния, как это сделано здесь:
setState((prevState) => {
return {
...prevState,
name: 'David'
};
});
// ИЛИ
setState((prevState) => {
return {
...prevState,
isEmployed: true
};
});
Если не расширить предыдущие значения состояния, другие свойства будут потеряны, поскольку состояние не поглощается автоматически при использовании хука useState
с объектом.
22. Помните, что динамическое добавление классов Tailwind в React не работает
Если вы используете Tailwind CSS для стилизации и хотите динамически добавить какой-либо класс, то следующий код не будет работать.
<div className={`bg-${isActive ? 'red-200' : 'orange-200'}`}>
Some content
</div>
Это происходит потому, что в конечный CSS-файл Tailwind CSS включит только те классы, которые присутствовали во время первоначального сканирования файла.
Таким образом, приведенный выше код динамически добавит класс bg-red-200
или bg-orange-200
в div, но его CSS не будет добавлен, и вы не увидите CSS, примененный к этому div.
Чтобы избежать этого, нужно изначально определить весь класс следующим образом:
<div className={`${isActive ? 'bg-red-200' : 'bg-orange-200'}`}>
Some content
</div>
Если у вас много классов, которые нужно условно добавить, можете определить объект с полными именами классов следующим образом:
const colors = {
purple: 'bg-purple-300',
red: 'bg-red-300',
orange: 'bg-orange-300',
violet: 'bg-violet-300'
};
Используйте это следующим образом:
<div className={colors['red']}>
Some content
</div>
23. Всегда добавляйте маршрут для страницы по умолчанию или ненайденной страницы при использовании React Router
При реализации маршрутизации в React с помощью React Router не забудьте включить маршрут для недействительного пути (Not Found Page — ненайденной страницы).
Ведь если кто-то перейдет по несуществующему маршруту, то увидит пустую страницу.
Чтобы настроить недействительный маршрут, можно использовать компонент недействительного маршрута в качестве последнего Route в списке маршрутов, как показано ниже.
<Routes>
<Route path="/" element={<Home />} />
.
.
.
<Route path="*" element={<NotFoundPage />} />
</Routes>
Если не хотите отображать компонент NotFoundPage
для недействительного маршрута, можете настроить перенаправление, чтобы автоматически перенаправлять пользователей на главную страницу в случае недействительного маршрута, как показано ниже:
<Routes>
<Route path="/" element={<Home />} />
.
.
.
<Route path="*" element={<Navigate to="/" />} />
</Routes>
24. Используйте оператор spread для упрощения передачи свойств объекта дочернему компоненту
Если у вас много свойств, которые нужно передать компоненту, то можно не передавать отдельные свойства, как здесь:
const Users = ({users}) => {
return (
<div>
{users.map(({ id, name, age, salary, isMarried }) => {
return (
<User
key={id}
name={name}
age={age}
salary={salary}
isMarried={isMarried}
/>
)
})}
</div>
)
}
Вместо этого можно использовать оператор spread, чтобы проще передать свойства таким образом:
const Users = ({users}) => {
return (
<div>
{users.map((user) => {
return (
<User key={user.id} {...user} />
)
})}
</div>
)
}
Однако не стоит злоупотреблять синтаксисом spread. Если у объекта много свойств, то лучше передать их вручную, как показано в первом случае.
25. Не забывайте, что перемещение состояния в пользовательский хук не освобождает от повторного рендеринга компонента
Изменение состояния внутри пользовательского хука, используемого в любом из компонентов, не означает, что не будет повторного рендеринга компонента, использующего этот хук.
Взгляните на приведенный ниже код:
export const useFetch = () => {
const [data, setData] = useState([]);
const [isLoading, setIsLoading] = useState(false);
// код для получения и обновления данных
return { data, isLoading };
};
const App = () => {
const { data, isLoading } = useFetch();
// возврат JSX
};
Как видите, useFetch
— пользовательский хук, поэтому при изменении состояния data
или isLoading
компонент App
будет отрендерен повторно.
Следовательно, если компонент App
рендерит другие дочерние компоненты, их прямые дочерние компоненты, как и косвенные дочерние компоненты, также будут повторно рендериться при изменении состояния data
или isLoading
из пользовательского хука useFetch
.
Поэтому рефакторинг компонента App
для перемещения состояния внутри пользовательского хука не означает, что компонент App
не будет отрендерен повторно.
Читайте также:
- ReacType (v21): низкий барьер входа и высокая планка разработки на React
- Топ-25 полезных советов для React-разработчиков. Часть 1
- ReactJS, Angular5 и Vue.js — какой фреймворк выбрать в 2018 году?
Читайте нас в Telegram, VK и Дзен
Перевод статьи Yogesh Chavan: Top 25 React Tips Every Developer Should Know — Part 2