Рассмотрим реализацию
useState, чтобы понять базовые принципы его работы.Весь код можно посмотреть в sandbox, сейчас же разберем по порядку.
Для начала нам нужны глобальные переменные:
let currentlyRenderingComponent = null;
let currentHookIndex = 0;
const componentHooks = new Map();
let pendingUpdates = [];
currentlyRenderingComponent указывает на компонент, который сейчас рендерится.currentHookIndex отслеживает порядок хуков внутри компонента.componentHooks связывает компоненты с их хуками.pendingUpdates имитирует очередь на обновление.Теперь создадим необходимые структуры данных:
class Hook {
constructor(initialState) {
this.memoizedState = initialState;
this.baseState = initialState;
this.queue = [];
}
}
class Update {
constructor(action) {
this.action = action;
this.next = null;
}
}
Класс
Hook представляет собой одно состояние внутри компонента. У каждого хука есть значение и очередь обновлений. Также мы хранить исходное значение, для сбросов или вычислений.Класс
Update описывает отдельное обновление состояния и формирует связанный список, чтобы обновления можно было применять по очереди.Вспомогательная функция для получения или создания хука:
function getOrCreateHook(index) {
let hooks = componentHooks
.get(currentlyRenderingComponent);
if (!hooks) {
hooks = [];
componentHooks
.set(currentlyRenderingComponent, hooks);
}
if (index >= hooks.length) {
hooks.push(new Hook());
}
return hooks[index];
}
Эта функция отвечает за доступ к нужному хуку в массиве хуков компонента. Если массива или самого хука еще нет, они создаются. Это важно для поддержки последовательного вызова хуков.
function dispatchAction(component, hook, action) {
const update = new Update(action);
hook.queue.push(update);
if (!pendingUpdates.includes(component)) {
pendingUpdates.push(component);
}
scheduleUpdate();
}
Функция
dispatchAction имитирует поведение setState: она создает объект обновления, добавляет его в очередь соответствующего хука и инициирует обновление компонента.В упрощенном понимании
scheduleUpdate имитирует постановку задач на выполнение через отложенный render с setTimeout. Реализация
useState:function useState(initialState) {
const hook = getOrCreateHook(currentHookIndex++);
if (hook.queue.length) {
let next = hook.baseState;
for (const item of hook.queue)
next = typeof item.action === "function"
? item.action(next)
: item.action;
hook.queue.length = 0;
hook.memoizedState = next;
hook.baseState = next;
} else if (hook.memoizedState === undefined) {
const value =
typeof initialState === "function"
? initialState()
: initialState;
hook.memoizedState = value;
hook.baseState = value;
}
const owner = currentlyRenderingComponent;
const dispatch = (action) => dispatchAction(owner, hook, action);
return [hook.memoizedState, dispatch];
}
Сначала получаем хук по текущему индексу — если такого еще нет, он создается.
Проверяем очередь хуков, если в ней накопились обновления, они применяются одно за другим: если обновление — это функция, то она вызывается с текущим значением, иначе используется переданное значение напрямую. После обработки обновлений очередь очищается, и новое значение сохраняется как актуальное состояние.
Если это первый вызов
useState и хук еще не был инициализирован, то начальное состояние устанавливается из initialState — это может быть как значение, так и функция (ленивая инициализация). Затем создается функция
dispatch, связанная с текущим компонентом и конкретным хуком, чтобы при вызове setState обновлялось только нужное состояние. В конце возвращается пара, аналогичная настоящему useState: текущее значение состояния и функция для его обновления.---
#React #Hooks #useState #Fiber #Frontend #JavaScript #8bitJS