Новый хук (произносится как «хак») для виртуально синхронных обновлений состояния.

Я представлю относительно простой хук, который создает псевдодвустороннюю привязку мгновенно обновляемой неизменной ссылки на состояние хука useState.

Боже… этот крючок — полный рот!

Мы все так или иначе боролись с управлением государством. В моем случае я изучил React примерно в то время, когда хуки продвигались как новый стандарт.

Теоретически это кажется достаточно простым; вы инициализируете переменную состояния с помощью ловушки useState, и она возвращает объект состояния, а также его метод установки.

// initialise the state with an initial value
const [myState, setMyState] = useState(initialValue);

Когда вызывается метод set, новое значение объекта состояния ставится в очередь для обновления, что запускает повторную визуализацию компонента.

setMyState(newValue);

Теперь мы полагаемся на другой хук, чтобы определить, когда состояние было обновлено таким асинхронным способом, а именно на метод useEffect. Это выполнит обратный вызов, который запускается изменением состояния.

useEffect(() => {
  // do something with the new state value
  ...
}, [myState]);

Уже очевидно, что ваше изменение состояния теперь отделено от какой-либо дальнейшей логики, которую вы, возможно, захотите выполнить вместе с обновленным состоянием.

Другая потенциальная авария может произойти при обновлении состояния в течение определенного интервала. Примером может быть увеличение счетчика один раз в секунду. Следующий код не будет работать должным образом:

// myState === 0
setInterval(() => {
  setMyState(myState + 1);
}, 1000);
// expected: myState => 1, 2, 3, 4, etc
// reality: myState => 1, 1, 1, 1, etc

Когда запускается метод setInterval, объект myState «запоминается» setInterval, навсегда начиная с 0 после каждого тика. У метода setMyState есть вариант, который может справиться с этой проблемой.

// myState === 0
setInterval(() => {
  setMyState(prevMyState => prevMyState + 1);
}, 1000);
// expected: myState => 1, 2, 3, 4, etc
// reality: myState => 1, 2, 3, 4, etc

Ловушка состояния знает о своем предыдущем состоянии и передает это значение стрелочной функции, которая выполняется при обновлении объекта myState. Это позволяет обойти отсутствие у метода setInterval осведомленности о текущем значении объекта состояния.

Что мне нравится в хуках, так это то, что вы можете использовать их в качестве строительных блоков для создания более надежных или специализированных пользовательских хуков. Мой смешанный опыт работы с хуками useState и useEffect привел меня к другому хуку под названием useRef. Есть два вкуса: неизменный и изменяемый.

Для целей этой статьи нас интересует изменяемый случай — использование его в качестве ссылки на копию значения нашего объекта состояния. Обновление этого объекта ref происходит мгновенно и не вызывает повторную визуализацию компонента.

Сами по себе useState и useRef могут быть мощными инструментами, но ни один из них не является идеальным решением для обработки кода с отслеживанием состояния. Так почему бы не объединить их? И как и когда мы будем их использовать?

Я предлагаю следующий хук (в vanilla js для базовой иллюстрации):

function useRefState(initialValue) {
  const [hookState, setHookState] = useState(initialValue);
  const hookRef = useRef(initialValue);
    
  const setState = (newVal) => {
    if (typeof newVal === "function") {
      hookRef.current = newVal(hookRef.current);
    } 
    else {
      hookRef.current = newVal;
    }
    setHookState(hookRef.current);
  }
  const getStateValue = () => {
    return hookRef.current;
  }  
  return [hookState, setState, getStateValue];
};

Во-первых, он инициализирует хуки, устанавливая начальные значения для объекта состояния, а также для объекта ref.

Следующая часть объявляет обновленный метод setState. Здесь происходит то, что он всегда сначала обновляет объект ref. Затем он устанавливает состояние с новым значением ссылки, гарантируя, что они синхронизированы друг с другом (по крайней мере, после обновления объекта состояния). Этот метод также позволяет использовать newVal как стрелочную функцию, которая может выполняться для обновления объекта hookRef.

Наконец, хук возвращает состояние, новый метод setState, а также метод GET, который возвращает текущее значение ссылки в массиве со структурой, аналогичной стандартному хуку useState.

Хук useRefState теперь можно использовать взаимозаменяемо с хуком useState.

// initialise the state and ref with an initial value
const [myState, setMyState, getMyStateValue] = useRefState(initialValue);

Состояние может быть обновлено как обычно.

setMyState(newValue);
// or for example
setMyState(prevState => prevState + 1);

Где этот новый хук вступает в свои права, так это возможность надежно отслеживать ожидаемое значение объекта состояния в любой момент времени в течение жизненного цикла объекта состояния через объект ref.

// myState === 0
// getMyStateValue() === 0
setInterval(() => {
  setMyState(getMyStateValue() + 1);
}, 1000);
// myState => 1, 2, 3, 4, etc
// getMyStateValue() => 1, 2, 3, 4, etc

И еще один пример, когда вам не нужно ждать, пока сработает обновление состояния:

// update the state to a new value through arrow function
setMyState(prevState => calculateNewStateValue(prevState));
// code being executed within the same flow
// do work with the expected new value of the state
doSomething(getMyStateValue());

Надеюсь, вы найдете этот шаблон проектирования полезным, когда вам нужен хак, или если вы точно знаете, что делаете, потому что знаете лучше :)

PS: Любителям машинописи вот зацепка во всей красе…