Мы стремительно приближаемся к 2025 году, в котором React, несомненно, по-прежнему будет одним из самых популярных вариантов для создания фронтенд-приложений.
И не потому, что у него нет недостатков, а благодаря огромному сообществу и массовой популярности, которую React приобрел за эти годы. Нельзя забывать и о 19-й версии React, которая многое обещает, так что React не сдаст своих позиций.
Трудно представить React без хуков, но, к сожалению, я часто вижу, как разработчики злоупотребляют ими. В результате им приходится решать проблемы с массивами зависимостей, бесполезными повторными рендерингами и всем тем хаосом, который они создали из-за глубокой уверенности в том, что в React все можно решить только с помощью хуков.
В этой статье я расскажу о пяти принципах, которые должен знать каждый начинающий программист, работающий с React. Они помогут улучшить и упростить код.
1. Не каждая функция — хук
Начнем с основ и посмотрим на определение хуков.
Хуки определяются с помощью функций JavaScript. Они представляют собой отдельный тип многократно используемой логики пользовательского интерфейса и могут вызываться только в конкретных местах.
Хуки могут выглядеть как функции, но следует помнить о некоторых отличиях:
- Хуки можно использовать только в функциональных компонентах или в пользовательских хуках.
- Имя хука всегда начинается с «use», за которым следует заглавная буква.
- Если пользовательский хук не включает вызовы других хуков, речь идет о функции, а не о хуке. Это важно знать, потому что так мы можем определить, есть ли внутри логика состояния или эффекты.
Создадим простой пользовательский хук под названием useBoolean, который бы удовлетворял этим требованиям. Можем использовать его для открытия/закрытия панелей, диалогов, показа/скрытия элементов и т. д.
Официальная документация рекомендует оборачивать все функции, которые возвращает хук, в useCallback, что мы и сделаем.
interface ICallbacks {
setFalse: () => void;
setTrue: () => void;
toggle: () => void;
}
const useBoolean = (initialValue: boolean): [boolean, ICallbacks] => {
const [value, setValue] = useState(initialValue);
const setFalse = useCallback(() => {
setValue(false);
}, []);
const setTrue = useCallback(() => {
setValue(true);
}, []);
const toggle = useCallback(() => {
setValue(curValue => !curValue);
}, []);
return [value, {setFalse, setTrue, toggle}];
}
Усвоив основы, мы сможем погрузиться глубже и лучше понять некоторые нюансы.
2. Повторный рендеринг имеет значение
Важно понимать, как работает React и что происходит, когда вы изменяете состояние компонента через функцию setter. На первый взгляд кажется, что после изменения состояния результат должен появиться немедленно. Но так ли это на самом деле?
Когда вы знаете, что происходит при изменении состояния, гораздо проще понять, когда и почему запускаются хуки useEffect или другие хуки, работающие с массивами зависимостей.
Рассмотрим простой пример. Представьте, что мы нажимаем кнопку в первый раз и вызываем onChangeText, передавая значение «newValue».
const [text, setText] = useState("defaultValue");
const onChangeText = (value: string) => {
// значение равно "newValue"
setText(value);
console.log(text); //Какое здесь будет значение?
}
Может показаться, что в консоли мы должны увидеть «newValue», но на самом деле там будет «defaultValue». Почему? Потому что новое значение будет доступно только после повторного рендеринга.
Здесь необходимо понимать следующие этапы:
- Мы изменяем состояние с помощью функции setter, указывая React выполнить действие.
- Этап рендера. React вызывает компонент для вычисления нового JSX, который будет возвращен.
- Этап коммита. После вычисления изменений React модифицирует DOM. Этот этап требует минимального количества действий.
- После выполнения вышеперечисленных шагов вы увидите визуальные изменения на экране («рендеринг в браузере»).
Помните: когда вы захотите изменить значение в состоянии, все эти шаги будут выполняться каждый раз.
3. useState не всегда является правильным вариантом
В React есть два способа управления состоянием компонента — useState и useReducer. Второй менее популярен, поскольку предназначен для более сложных объектов в состоянии и, честно говоря, на первый взгляд кажется слишком сложным для начинающих программистов, но это не так.
В то же время useState выглядит очень просто и понятно, поэтому новички нередко используют его чаще, чем требуется.
В зависимости от характера взаимодействия с пользователем, useState предназначен для управления состоянием, касающимся перерисовки компонентов. Если вы хотите запомнить что-то без проведения рендеринга, то, вероятно, не стоит помещать это в состояние. Лучшим вариантом будет использование useRef.
Вам не нужен useState в следующих случаях:
- Вы хотите запомнить некоторые значения при повторном рендеринге, не показывая их пользователю.
- У вас уже есть данные в состоянии или вы получаете их через пропсы, но вам нужно их преобразовать. Не нужно хранить это новое значение в новом объекте useState — создайте новую переменную и работайте с ней, не вызывая бесполезные повторные рендеринги.
- Вам нужно сохранить значение в состоянии, если вы хотите перерисовать компонент при изменении значения (самые распространенные случаи — показ/скрытие панелей, спиннеры, сообщения об ошибках и изменение массивов).
Упростите свой код, изменив его с этого:
/**
Такой подход приводит к бесполезным повторным рендерингам и ненужному
использованию UseEffect.
Когда имя или описание изменяется
и React повторно рендерит компонент,
React проверяет, есть ли функциональность, которая зависит от этих значений.
useEffect будет срабатывать при изменении названия или описания,
создавая повторный рендеринг.
**/
const [name, setName] = useState('name');
const { description, index } = props;
const [fullName, setFullName] = useState('');
useEffect(()=>{
setFullName(`${name} - ${description}`);
},[name, description]);
На этот вариант:
/**
Мы можем использовать поведение React по умолчанию и получать правильное значение при изменении названия
или описания, не вызывая повторного рендеринга.
**/
const [name, setName] = useState();
const { description, index } = props;
const nameWithDescription = `${name} - ${description}`;
4. useEffect требует крайней осторожности
Мы использовали определенные методы жизненного цикла с компонентами Class в конкретных случаях. Начиная с версии React 16.8, может показаться, что у нас есть один хук useEffect для всего.
Это не так. Но из-за отсутствия документации и невероятного ажиотажа вокруг хуков в 2019 году появилась тенденция использовать useEffect везде, где только можно.
Обратимся к официальной документации:
useEffect — это хук React, который позволяет синхронизировать компонент с внешней системой.
Но фактически мы используем useEffect гораздо чаще, чем нужно. Он отлично подходит для получения данных при монтировании компонента, но, к сожалению, начинающие программисты склонны менять состояние с помощью этого хука, что не является лучшим решением.
Остановитесь и проанализируйте свой код, если обнаружите, что пишете один useEffect за другим внутри одного компонента. Обычно хуки useEffect не нужны, и вы можете быстро от них избавиться.
Вам не нужен useEffect в следующих случаях:
- Вам необходимо обрабатывать события пользователя (клики). Если вы знаете, какие действия вызывают определенную логику, не используйте useEffect для вызова этой логики.
- Вам нужно преобразовать данные для рендеринга, например объединить строки из состояния или пропсов. Если поместить реактивные значения или логику в массив зависимостей, можно вызывать useEffect слишком часто, что приведет к бесконечным циклам.
Вам нужно использовать useEffect, если вы хотите получать данные при монтировании компонентов, задавать интервалы и синхронизировать состояние с другими системами.
5. Не бойтесь применять useRef
К сожалению, хук useRef недооценивают. Он не входит в число самых популярных хуков, но он полезен. Зная, как и где его использовать, можно добиться отличных результатов.
Начнем с основ.
useRef — это хук React, который позволяет ссылаться на значение, не нужное для рендеринга (из официальной документации React).
React запомнит значение, которое вы создаете с помощью useRef, независимо от того, разрабатываете ли вы Javascript-объект, ссылающийся на узел в DOM, или простое значение, и оно не будет потеряно при повторном рендеринге.
Что это нам дает?
- Мы можем легко получить доступ к элементам в DOM. Например, можно получить значение поля ввода, сфокусироваться на определенных элементах, получить их высоту и ширину, прокрутить до определенной части экрана и многое другое.
- Вы можете запомнить любые данные, которые вам нужны, без повторного рендеринга компонентов. Например, если вам нужен счетчик или таймер, выберите useRef, но не useState.
Примеры:
// ссылка на число
const refCount= useRef(0);
// ссылка на входящий элемент
const refInputField = useRef(null);
/**
Чтобы получить доступ к значению, нужно использовать свойство current.
UseRef не вызывает повторных рендерингов, поэтому вы можете использовать их внутри UseEffect,
не заботясь о массиве зависимостей
*/
const onClick = () => {
refCount.current = refCount.current + 1;
refInputField.current.focus();
}
return (
<>
<button onClick={onClick}>
Click me!
</button>
<input ref={refInputField}></input>
</>
);
Хуки — это здорово, и вы должны их использовать. Вы сможете добиться многого, если поймете, как работает React, и будете правильно применять хуки.
Вот и все советы для начинающих разработчиков React. Надеюсь, они вам будут полезны.
Читайте также:
- Пишите React-компоненты на профессиональном уровне
- Компоненты высшего порядка в React Virtualized
- Как работает React Fiber Reconciler?
Читайте нас в Telegram, VK и Дзен
Перевод статьи Inna Sinicka: Learning React: 5 Important Principles About Hooks You Have To Know