От автора: я большой поклонник нового API хуков. Тем не менее, у него есть некоторые странные ограничения относительно того, как вам следует его использовать. Здесь я опишу модель, с помощью которой те, кто пытается понять причины этих правил, могут представить себе, как именно используются в React хуки.
ПРЕДУПРЕЖДЕНИЕ: API хуков является экспериментальной функцией
Эта статья посвящена API хуков, который в настоящее время является экспериментальным предложением. Здесь вы можете найти документацию по стабильному API React.
Как работают хуки
Я слышал, как некоторые люди не могли разобраться с «магией», связанной с новым предложением API хуков, поэтому я подумал, что попытаюсь объяснить, как работает предложение синтаксиса хотя бы на поверхностном уровне.
Правила хуков
Существует два основных правила использования, которым, как декларирует основная команда React, вам нужно следовать, чтобы использовать хуки, описанные ними в документации предложения хуков.
Не вызывайте хуки внутри циклов, условий или вложенных функций
Вызывайте хуки только из функций React
Последнее, на мой взгляд, самоочевидно. Чтобы добавить поведение функциональному компоненту, вам нужно как-то связать это поведение с компонентом.
Первое, как мне кажется, может сбивать с толку, поскольку может показаться неестественным программирование с использованием API, и это то, что я хочу рассмотреть в этой статье.
Управление состоянием в хуках — все дело в массивах
Чтобы получить более ясную ментальную модель, давайте посмотрим, как выглядит простая реализация API хуков.
Обратите внимание, что это только один из возможных способов реализации API. API не обязательно работает именно так внутри. Также это только предложение. Все это может измениться в будущем.
Как мы можем реализовать useState()
?
Давайте рассмотрим пример, чтобы продемонстрировать, как может работать реализация состояния наведения. Начнем с компонента:
function RenderFunctionComponent() { const [firstName, setFirstName] = useState("Rudi"); const [lastName, setLastName] = useState("Yardley"); return ( <Button onClick={() => setFirstName("Fred")}>Fred</Button> ); }
Идея API хуков заключается в том, что вы можете использовать функцию сеттера, возвращаемую в качестве второго элемента массива из функции хука, и этот сеттер будет управлять состоянием, которым управляет хук.
Итак, что React будет делать с этим?
Давайте рассмотрим, как это может работать внутри React. Приведенное ниже может работать внутри контекста для рендеринга конкретного компонента. Это означает, что хранящиеся здесь данные размещаются на один уровень дальше от отображаемого компонента. Это состояние не используется совместно с другими компонентами, но поддерживается в диапазоне, доступном для последующего рендеринга конкретного компонента.
1) Инициализация
Создайте два пустых массива: seters
и state
. Установите курсор на 0
Инициализация: два пустых массива, курсор — 0
2) Первый рендеринг
Запустите функцию компонента в первый раз. Каждый вызов useState(), при первом запуске, вводит функцию сеттера (привязанную к позиции курсора) в массив setters, а затем вводит определенное состояние в массив state.
Первый рендеринг: элементы записываются в массивы при перемещении курсора
3) Повторный рендеринг
При всех последующих рендерингах курсор сбрасывается, и эти значения просто считываются из массива.
Последующие рендеринги: элементы считываются из массивов при перемещении курсора
4) Обработка событий
Каждый сеттер имеет ссылку на позицию курсора, поэтому при запуске вызова любого сеттера изменяется значение состояния в этой позиции в массиве состояний.
Сеттеры «помнят» свой индекс и устанавливают память в соответствии с этим
И нативная реализация
В этом примере кода демонстрируется эта реализация:
let state = []; let setters = []; let firstRun = true; let cursor = 0; function createSetter(cursor) { return function setterWithCursor(newVal) { state[cursor] = newVal; }; } // Псевдокод для хелпера useState export function useState(initVal) { if (firstRun) { state.push(initVal); setters.push(createSetter(cursor)); firstRun = false; } const setter = setters[cursor]; const value = state[cursor]; cursor++; return [value, setter]; } // Код нашего компонента в котором используются хуки function RenderFunctionComponent() { const [firstName, setFirstName] = useState("Rudi"); // курсор: 0 const [lastName, setLastName] = useState("Yardley"); // курсор: 1 return ( <div> <Button onClick={() => setFirstName("Richard")}>Richard</Button> <Button onClick={() => setFirstName("Fred")}>Fred</Button> </div> ); } // Это что-то наподобие симуляции цикла рендеринга Reacts function MyComponent() { cursor = 0; // сброс курсора return <RenderFunctionComponent />; // рендеринг } console.log(state); // Перед рендерингом: [] MyComponent(); console.log(state); // Первый рендеринг: ['Rudi', 'Yardley'] MyComponent(); console.log(state); // Последующий рендеринг: ['Rudi', 'Yardley'] // клик кнопки 'Fred' console.log(state); // После клика: ['Fred', 'Yardley']
Почему порядок важен
Теперь, что произойдет, если мы изменим порядок хуков для цикла рендеринга, основываясь на каком-то внешнем факторе или даже состоянии компонента? Давайте сделаем то, от чего нас предостерегает команда React:
let firstRender = true; function RenderFunctionComponent() { let initName; if(firstRender){ [initName] = useState("Rudi"); firstRender = false; } const [firstName, setFirstName] = useState(initName); const [lastName, setLastName] = useState("Yardley"); return ( <Button onClick={() => setFirstName("Fred")}>Fred</Button> ); }
Это противоречит правилам!
Здесь у нас вызов useState размещается в условном выражении. Давайте посмотрим, какой хаос это создает в системе.
Первый рендеринг некорректного компонента
Рендеринг дополнительного «плохого» хука, который позже будет удален
На этом этапе наши экземпляры vars firstName и lastName содержат правильные данные, но давайте посмотрим, что будет при втором рендеринге.
Второй рендеринг некорректного компонента
Удаляя хук между рендерингами, мы получаем ошибку
Теперь как для firstName, так и для lastName задано «Rudi», поскольку наше хранилище состояний становится непоследовательным. Это явно ошибочно и не работает, но это дает нам представление о том, почему правила для хуков сформулированы таким образом.
Команда React устанавливает правила использования, потому что несоблюдение их приведет к несогласованности данных.
Представьте хуки, как работу с набором массивов, и вы не будете нарушать правила
Итак, теперь вам должно быть понятно, почему нельзя вызывать хук use
в условных выражениях или циклах. Поскольку мы имеем дело с курсором, указывающим на набор массивов, если вы измените порядок вызовов внутри рендеринга, курсор не будет соответствовать данным, и ваши вызовы не будут указывать на правильные данные или обработчики.
Таким образом, стоит представить себе хуки, как набор массивов, которым нужен последовательный курсор. Если вы это сделаете, все должно работать.
Заключение
Надеюсь, я представил более понятную ментальную модель того, что происходит под капотом с новым API хуков. Помните об этих вещах, будьте осторожны в отношении последовательности и использования API хуков.
Хуки — эффективный плагин API для компонентов React. Многие уже в восторге от него, и на то есть причины. Если вы представляете себе их в виде модели, в которой состояние существует как набор массивов, тогда вы не должны нарушать правила их использования.
Я надеюсь, что в будущем я рассмотрю метод useEffects и попытаюсь сравнить его с методами жизненного цикла компонента React.
Автор: Rudi Yardley
Источник: https://medium.com/
Редакция: Команда webformyself.