предисловие
Базовое использование React Hooks,официальная документацияуже очень подробно. Это третья статья из серии, посвященной механизму реализации хуков.
- Погружение в хуки React — useState
- Погружение в хуки React — useEffect
- Адрес личного блога🍹🍰 fe-код
useState
В первых двух статьях был проанализирован механизм выполнения useState и useEffect.Если вы хотите узнать больше о хуках, вы можете смоделировать и реализовать связанные функции в соответствии с соответствующими характеристиками хуков, или вы можете перейти непосредственно к просмотруисходный код. Студенты, которые не понимают некоторых основных особенностей хуков, могут обратиться к документации или моим первым двум статьям.
useState — это основа схемы хуков, которая позволяет нам создавать у компонентов собственное состояние, не полагаясь на классы. Его использование простое:
const [count, setCount] = useState(0);
И мы всегда должны помнить две вещи:1. useState — это функция 2. Каждое обновление фактически запускает ее повторное выполнение (как упоминалось в предыдущей статье, потому что весь функциональный компонент выполняется повторно).
Это так просто:
function useState(initialState) {
const state = initialState;
const setState = newState => state = newState;
return [state, setState];
}
Конечно, сейчас он неполный, потому что просто не может обновлять данные. Даже если мы инициируем обновление компонента, когда мы устанавливаем состояние, а затем обновляем состояние использования, в конце концов, это просто начальное состояние, полученное при повторной инициализации. Поскольку состояние каждого useState является локальной переменной, обновление эквивалентно его повторному созданию один раз, и последнее значение не может быть получено. Итак, нам нужно сохранить государство.
Самое простое — это глобальные переменные, конечно React этого не реализует. Для хуков React использует связанный список.Если вам интересно, вы можете пойти и посмотреть сами.Это всего лишь имитационная реализация.
const HOOKS = []; // 全局的存储 hook 的变量
let currentIndex = 0; // 全局的 依赖 hook 执行顺序的下标
function useState(initialState) {
HOOKS[currentIndex] = HOOKS[currentIndex] || initialState; // 判断一下是否需要初始化
const memoryCurrentIndex = currentIndex; // currentIndex 是全局可变的,需要保存本次的
const setState = newState => HOOKS[memoryCurrentIndex] = newState;
return [HOOKS[currentIndex++], setState]; // 为了多次调用 hook,每次执行 index 需要 +1
}
Мы сохраняем все хуки в переменной глобального массива и просто ищем соответствующий индекс при каждом обновлении.Вот почему необходимо следить за тем, чтобы порядок выполнения хуков был одинаковым до и после обновления.. Представим, что в первом прогоне было выполнено четыре хука, а индекс был увеличен до 3, а при обновлении, из-за суждений и других причин, исходный третий хук не был выполнен, поэтому значение, полученное четвертым хуком, не равно то же неправильно? .
Давайте расширим немного дальше, инициализацию useState можно передать функциям, а setState — то же самое, так что вы можете добавить немного больше суждений.
function useState(initialState) {
HOOKS[currentIndex] = HOOKS[currentIndex]
|| (typeof initialState === 'function' ? initialState() : initialState);
const memoryCurrentIndex = currentIndex; // currentIndex 是全局可变的,需要保存本次的
const setState = p => {
let newState = p;
// setCount(count => count + 1) 判断这种用法
if (typeof p === 'function') newState = p(HOOKS[memoryCurrentIndex]);
// 如果设置前后的值一样,就不更新了
if (newState === HOOKS[memoryCurrentIndex]) return;
HOOKS[memoryCurrentIndex] = newState;
};
return [HOOKS[currentIndex++], setState];
}
Tick
Конечно, теперь мы просто обновляем соответствующее состояние, не уведомляя функцию обновления рендера, мы можем написать простой инструмент.
const HOOKS = []; // 全局的存储 hook 的变量
let currentIndex = 0; // 全局的 依赖 hook 执行顺序的下标
const Tick = {
render: null,
queue: [],
push: function(task) {
this.queue.push(task);
},
nextTick: function(update) {
this.push(update);
Promise.resolve(() => {
if (this.queue.length) { // 一次循环后,全部出栈,确保单次事件循环不会重复渲染
this.queue.forEach(f => f()); // 依次执行队列中所有任务
currentIndex = 0; // 重置计数
this.queue = []; // 清空队列
this.render && this.render(); // 更新dom
}
}).then(f => f());
}
};
В React обновление setState синхронное, но мы его не воспринимаем, по крайней мере кажется, что оно асинхронное. Это связано с тем, что React сам реализует набор управления транзакциями. Возможности ограничены, поэтому используйте Promise, чтобы заменить его здесь, аналогично nextTick в Vue.
const setState = p => {
// ···
Tick.nextTick(() => {
HOOKS[memoryCurrentIndex] = newState;
});
};
useEffect
useEffect действует как роль жизненного цикла в классе, и мы используем его больше для уведомления об обновлениях и устранения побочных эффектов. При понимании useState общий принцип тот же. Есть только еще один массив зависимостей, и если каждый элемент в массиве зависимостей такой же, как и последний, его не нужно обновлять, и наоборот.
function useEffect(fn, deps) {
const hook = HOOKS[currentIndex];
const _deps = hook && hook._deps;
// 判断是否传了依赖,没传默认每次更新
// 判断本次依赖和上次的是否全部一样
const hasChange = _deps ? !deps.every((v, i) => _deps[i] === v) : true;
const memoryCurrentIndex = currentIndex; // currentIndex 是全局可变的
if (hasChange) {
const _effect = hook && hook._effect;
setTimeout(() => {
// 每次先判断一下有没有上一次的副作用需要卸载
typeof _effect === 'function' && _effect();
// 执行本次的
const ef = fn();
// 更新effects
HOOKS[memoryCurrentIndex] = {...HOOKS[memoryCurrentIndex], _effect: ef};
})
}
// 更新依赖
HOOKS[currentIndex++] = {_deps: deps, _effect: null};
}
Вы можете видеть, что useEffect сохраняет два значения в HOOKS, одно является зависимостью, а другое побочным эффектом, Время сохранения соответствует порядку его выполнения. Поскольку useEffect необходимо выполнить после монтирования dom, setTimeout используется для простой симуляции, чего нет в React.
useReducer
useReducer хорошо понятен и может рассматриваться как оболочка вокруг useState, позволяющая нам более интуитивно управлять состоянием. Сначала вспомним, как его использовать:
const reducer = (state, action) => {
switch (action.type) {
case 'increment':
return {total: state.total + 1};
case 'decrement':
return {total: state.total - 1};
default:
throw new Error();
}
}
const [state, dispatch] = useReducer(reducer, { count: 0});
// state.count ...
То есть нам на самом деле просто нужно совместить useState и редюсер.
function useReducer(reducer, initialState) {
const [state, setState] = useState(initialState);
const update = (state, action) => {
const result = reducer(state, action);
setState(result);
}
const dispatch = update.bind(null, state);
return [state, dispatch];
}
useMemo & useCallback
Оба они используются для оптимизации производительности React за счет зависимостей и предназначены для обработки некоторых дорогостоящих вычислений. useMemo возвращает значение, а useCallback возвращает функцию.
useMemo
function useMemo(fn, deps) {
const hook = HOOKS[currentIndex];
const _deps = hook && hook._deps;
const hasChange = _deps ? !deps.every((v, i) => _deps[i] === v) : true;
const memo = hasChange ? fn() : hook.memo;
HOOKS[currentIndex++] = {_deps: deps, memo};
return memo;
}
useCallback
function useCallback(fn, deps) {
return useMemo(() => fn, deps);
}
резюме
В этом простом симуляторе реализованы некоторые хуки React. Если вам интересно, вы можете улучшить его самостоятельно. Если вы обнаружите какие-либо проблемы, вы можете указать на это в комментариях, и я обновлю его со временем.
- полный код здесьhook.js
- Способ применения следующий:
function render() {
const [count, setCount] = useState(0);
useEffect(() => {
const time = setInterval(() => {
setCount(count => count + 1);
}, 1000)
// 清除副作用
return () => {
clearInterval(time);
}
}, [count]);
document.querySelector('.add').onclick = () => {
setCount(count + 1);
};
document.querySelector('#count').innerHTML = count;
}
// 绑定 render
Tick.render = render;
render();
Справочная статья
группа обмена
Группа WeChat: Отсканируйте код, чтобы ответить, чтобы присоединиться к группе.
постскриптум
Если вы это видите, и эта статья вам полезна, надеюсь, вы сможете поддержать автора своими ручонками, спасибо 🍻. Если в тексте что-то не так, укажите на это и поделитесь. Хорошо, я снова потратил впустую время всех, спасибо за чтение, увидимся в следующий раз!
- Репозиторий статей 🍹🍰fe-код
Заинтересованные студенты могут обратить внимание на мой публичный номерпередний двигатель, весело и познавательно.