Я большой фанат новых 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

Предыдущая статья9 лучших примеров макетов сайта и идей для веб-дизайна в 2018
Следующая статья9 советов для быстрой работы в UNIX и Linux терминале