Я большой фанат новых API хуков. Но знаете, у них есть странные ограничения по применению. Для тех, кто жаждет понять причины появления этих правил, здесь я представлю модель понимания, того, как их применять.
Предупреждаю: Хуки сейчас в статусе эксперимента
Эта статья об API хуков, который на данный момент находится в тестовом режиме. Документацию по стабильной версии React API можно найти здесь.

Как работают хуки?
Я слышал о борьбе некоторых людей с предложенным проектом API хуков. Поэтому здесь я попытаюсь разъяснить, как работает предложенный синтаксис, по крайней мере, поверхностно.
Правила хуков
Есть два основных правила, упоминаемые командой React, которым вы должны следовать, используя хуки. Они описаны в документации.
- Не вызывайте хуки внутри циклов, условий или вложенных функций
- Вызывайте хуки только из функций React
Последнее, я думаю, очевидно. Чтобы назначить поведение функциональному компоненту, нужно иметь возможность как-то ассоциировать это поведение с этим компонентом.
А вот первый пункт может сбить с толку. Это кажется не естественным, для программ, использующих подобные API и именно об этом я и хочу поговорить.
Управление состояниями в хуках — всё дело в массивах
Чтобы внести ясность в эту модель понимания, давайте посмотрим на простейшую реализацию API хуков.
Пожалуйста имейте в виду, что это всего лишь рассуждения и один из возможных способов реализации API, чтобы показать вам, как можно относиться к хукам. Речь пойдёт не о внутреннем устройстве API. Более того, это не окончательная реализация хуков, в будущем всё может измениться.
Как можно реализовать `useState()` ?
Давайте разберём пример, чтобы продемонстрировать реализацию state hook.
Начнём с компонента:
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi");
const [lastName, setLastName] = useState("Yardley");
return (
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
);
}
Идея API хуков в том, что вы можете использовать сеттер-функцию, возвращённую в качестве второго элемента массива из хуки-функции. Этот сеттер будет контролировать state, под управлением хуков.
Итак, что же React будет с этим делать?
Давайте разберёмся, как это будет работать внутри React. Этот код будет работать в контексте исполнения, для отображения определённого компонента. Это значит, что данные, которые здесь хранятся, «живут» за пределами этого компонента. Этот state не доступен другим компонентам, но поддерживается в пространстве, которое доступно для дальнейшего рендеринга определённого компонента.
1) Инициализация
Создаём два пустых массива: `setters` и `state`
Устанавливаем курсор на 0

2) Первый рендер
Запускаем функцию компонента в первый раз.
С каждым вызовом useState(), при первом запуске, в массив setters записывается сеттер-функция (с привязкой к позиции курсора), а затем state в массив state.

3) Последующий рендер
Каждый последующий рендер сбрасывает курсор, а те значения просто считываются из каждого массива.

4) Обработка событий
Каждый сеттер ссылается на свою позицию курсора. Поэтому вызов любого setter элемента будет менять значение state, в массиве state, в соответствии с позицией курсора.

«Наивная» реализация
Этот пример демонстрирует «наивную» реализацию:
let state = [];
let setters = [];
let firstRun = true;
let cursor = 0;
function createSetter(cursor) {
return function setterWithCursor(newVal) {
state[cursor] = newVal;
};
}
// This is the pseudocode for the useState helper
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];
}
// Our component code that uses hooks
function RenderFunctionComponent() {
const [firstName, setFirstName] = useState("Rudi"); // cursor: 0
const [lastName, setLastName] = useState("Yardley"); // cursor: 1
return (
<div>
<Button onClick={() => setFirstName("Richard")}>Richard</Button>
<Button onClick={() => setFirstName("Fred")}>Fred</Button>
</div>
);
}
// This is sort of simulating Reacts rendering cycle
function MyComponent() {
cursor = 0; // resetting the cursor
return <RenderFunctionComponent />; // render
}
console.log(state); // Pre-render: []
MyComponent();
console.log(state); // First-render: ['Rudi', 'Yardley']
MyComponent();
console.log(state); // Subsequent-render: ['Rudi', 'Yardley']
// click the 'Fred' button
console.log(state); // After-click: ['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 находится внутри условия. Посмотрите к какому беспорядку это привело.
Плохой компонент. Первый рендер

На данный момент, экземпляры переменных firstName и lastName содержат верные данные, но посмотрите, что произойдёт на втором рендере:
Плохой компонент. Второй рендер

Теперь firstName и lastName содержат “Rudi”, так как state хранилище стало непоследовательным. Это не рабочий пример с явной ошибкой, но он даёт понимание, почему появились эти правила для хуков.
Команда React оговаривает правила использования, потому что их несоблюдение приведёт к несогласованности данных
Думайте о хуках, как о манипуляциях с массивами и не нарушайте правил
Теперь должно быть понятно, почему нельзя вызывать `use` хуки внутри условий и циклов. Потому что у нас будут проблемы с курсором, указывающим на соответствие в массивах. Если изменить порядок вызовов в течение рендера, курсор не будет указывать на верные данные или обработчики.
Управляйте хуками, думая о массивах, которым нужен курсор в правильном месте.
Заключение
Надеюсь, я смог пролить свет на то, что происходит под капотом API хуков. Помните о том, что истинная ценность здесь заключается в возможности группировать задачи. Поэтому внимательно относитесь к порядку и использованию API хуков. Это будет вознаграждено.
Hooks — это эффективный API плагин для React Components. Некоторые разработчики в восторге от его появления и на то есть причины. Если вы будете думать о состояниях, как о наборе массивов, то у вас не возникнет проблем с правилами React.
В будущем, надеюсь увидеть метод useEffects и сравнить его с методами жизненного цикла компонентов React.
Перевод статьи Rudi Yardley : React hooks: not magic, just arrays





