- 1. Начни с хорошего фундамента
- 2. Используй плагин ESLint
- 3. Синхронизация побочных эффектов с состоянием
- 4. Накручивание производительности
- 5. Накручивание тестирования hooks
Функция React Hooks
была предложена в октябре 2018 года и выпущена спустя ~4 месяца, в феврале 2019 года. С тех пор люди быстро изучают и применяют hooks
, так как они значительно упрощают управление состоянием и побочными эффектами в приложениях.
React Hooks
требует некоторого изменения в том, как ты думаешь о жизненных циклах компонентов React
, state
и побочных эффектах
, и можно легко попасть в проблемные сценарии. Итак, давай посмотрим с какими подводными камнями ты можешь столкнуться и как можешь изменить свое мышление, чтобы избежать их.
1. Начни с хорошего фундамента
Документация по React Hooks великолепна, и я настоятельно рекомендую прочесть её, особенно часто задаваемые вопросы, в которых много полезной информации. Так что удели себе добрый час или два и просто читай документацию. Это даст тебе большой обзор hooks
и сэкономит много времени.
Кроме того, не пропускай выступления React Conf Софи, Дэн и Райан, которые представили hooks.
Также мною написан небольшой обзор о hooks
здесь.
Чтобы избежать первой ошибки: прочти документацию и FAQ
2. Используй плагин ESLint
Примерно в то время, когда был выпущен Hooks
, был создан и выпущен пакет eslint-plugin-react-hooks. У него есть два правила: rules-of-hooks
и exhaustive-deps
. Рекомендуемая по умолчанию конфигурация этих правил состоит в том, чтобы установить rules-of-hooks
на error
, а exhaustive-deps
на warning
.
Настоятельно советую установить, использовать и следовать этим правилам. Он не только поймает реальные ошибки, которые ты легко можешь пропустить, но также научит вещам о hooks
в процессе (не говоря уже об удивительной функции авто-исправления).
3. Синхронизация побочных эффектов с состоянием
До hooks, у нас был красивый и понятный API-компонент, который позволял сообщать React, когда тот должен делать определенные вещи:
class LifecycleComponent extends React.Component {
constructor() {
// initialize component instance
// инициализировать экземпляр компонента
}
componentDidMount() {
// run this code when the component is first added to the page
// запустить этот код при первом добавлении компонента на страницу
}
componentDidUpdate(prevProps, prevState) {
// run this code when the component is updated on the page
// запустить этот код, когда компонент обновляется на странице
}
componentWillUnmount() {
// run this code when the component is removed from the page
// запустить этот код, когда компонент удаляется со страницы
}
render() {
// call me anytime you need some react elements...
// вызывай меня в любое время, когда нужны react элементы...
}
}
Написание таких компонентов работало очень хорошо в течение многих лет и до сих пор работает (и будет работать в обозримом будущем). Hooks
имеют много преимуществ, но одно из моих любимых - это то, что они делают компоненты более декларативными, так как позволяют нам перестать думать о том, когда что-то должно произойти в жизненном цикле компонента (что не имеет значения на самом деле) и больше о когда что-то должно происходить в связи с изменениями состояния (что имеет гораздо большее значение).
Итак, теперь мы имеем:
function HookComponent() {
React.useEffect(() => {
// Этот побочный эффект здесь для синхронизации глобального состояния
// с состоянием этого компонента.
return function cleanup() {
// Эта функция убирает предыдущий побочный эффект перед запуском нового.
};
// Поэтому эти зависимости нужны что бы при их изменении
// этот побочный эффект и его функция очистки запускались..
}, [when, any, ofThese, change]);
React.useEffect(() => {
// Этот побочный эффект будет повторяться
// при каждом повторном рендеринге этого компонента,
// чтобы убедиться что то, что он делает, никогда не устареет.
});
React.useEffect(() => {
// Этот побочный эффект никогда не сможет устареть,
// так как он не имеет никаких зависимостей
}, []);
return; /* some react elements */
}
С React Hooks
ты должен думать в контексте того, когда побочные эффекты должны запускаться, а именно о синхронизации состояния побочных эффектов с состоянием приложения, а не о жизненных циклах компонента. Понимание этого требует некоторого времени, но идея настолько мощная, что как только ты разберешься с ней - количество ошибок в приложениях, благодаря дизайну API, существенно уменьшатся.
Помни, что использовать пустой массив []
в useEffect
стоит лишь в том случае, если ты уверен, что код внутри него никогда не устареет, а не потому что этот побочный эффект нужно запускать исключительно при начальной сборке компонента.
Думай о синхронизации побочных эффектов с состоянием, а не о жизненных циклах
4. Накручивание производительности
По какой-то причине, некоторых это ужасает:
function MyComponent() {
function handleClick() {
console.log("clicked some other component");
}
return <SomeOtherComponent onClick={handleClick} />;
}
Есть 2 причины по которым возникает такая реакция:
- Мы определяем функцию внутри компонента, то есть она переопределяется каждый раз при рендеринге
<MyComponent />
. - Мы передаем вновь определенную функцию в качестве свойства (props) в
<SomeOtherComponent />
, что означает, что она не может быть должным образом оптимизирована с помощьюReact.memo
,React.PureComponent
илиshouldComponentUpdate
, и компонент будет страдать от “ненужных повторных рендерингов”.
Во-первых, движки JavaScript (даже те, что на мобильных устройствах) чрезвычайно быстро определяют функции. Ты вряд ли столкнешься с проблемой переопределения слишком большого количества функций.
Во-вторых, “ненужные повторные рендеринги” не обязательно ухудшают производительность. Тот факт, что компонент перерисовывается не означает, что DOM
будет обновлен (обновление DOM может быть медленным). React
отлично справляется с оптимизацией, поэтому тебе не нужно делать “странные” вещи с кодом, чтобы сделать его быстрым. Он быстр по умолчанию.
Если “ненужные повторные рендеринги” приложения приводят к замедлению твоего приложения, сначала выясни, почему рендеринг медленный. Если рендеринг приложения настолько медленный, что несколько дополнительных повторных визуализаций приводят к заметному замедлению - у тебя, вероятно, всё ещё будут проблемы с производительностью, когда ты достигнешь “необходимые повторные рендеринги”. Как только исправишь то, что делает рендеринг медленным, сможешь обнаружить, что “ненужные повторные рендеринги” больше не доставляют проблем.
Если определишь, что “ненужные повторные рендеринги” вызывают проблемы с производительностью, сможешь использовать встроенные API-интерфейсы оптимизации производительности, которые предлагает React
. Например: React.memo
, React.useMemo
и React.useCallback
. Помни, иногда ты можешь применить оптимизацию производительности, и твое приложение на самом деле будет работать медленнее! Так что первым делом измерь производительность!
Также помни, production версия React быстрее, чем development версия.
React работает по умолчанию быстро, так что прежде чем применять оптимизацию, изучи стоит ли это делать.
5. Накручивание тестирования hooks
Некоторые обеспокоены тем, что когда делается рефакторинг для hooks
, нужно переписывать тесты вместе со всеми их компонентами. Необходимость в этом зависит от того, как были написаны тесты.
Ключевое отличие между enzyme и React Testing Library заключается в том, что в первом случае чаще всего ты тестируешь реализацию компонента, а во втором - нет. Независимо от того, реализован ли компонент через hooks
или как class
, это деталь реализации компонента. Поэтому, если твой тест написан таким образом, который тестирует реализацию, то рефакторинг компонента в hooks
естественным образом приведет к поломке тестов.
Конечному пользователю по сути не важно, написаны компоненты с помощью hooks
или же class
. Они просто заботятся о возможности взаимодействия с тем, что эти компоненты выводят на экран. Так что, если твои тесты взаимодействуют с тем, что отображается, то не имеет значения, как этот материал отображается на экране. Всё и так будет работать независимо от того, используешь ты class
или hooks
.
Избегай тестирования деталей реализации.