парсинг useState
использование состояния использования
Обычно мы используем метод useState следующим образом.
function App() {
const [num, setNum] = useState(0);
const add = () => {
setNum(num + 1);
};
return (
<div>
<p>数字: {num}</p>
<button onClick={add}> +1 </button>
</div>
);
}
Используя useState, мы сначала моделируем приблизительную функцию
function useState(initialValue) {
var value = initialValue
function setState(newVal) {
value = newVal
}
return [value, setState]
}
В этом коде есть проблема, при выполнении useState каждый раз будет инициализироваться var _val = initialValue;
Таким образом, мы можем сохранить состояние в виде замыканий.
const MyReact = (function() {
// 定义一个 value 保存在该模块的全局中
let value
return {
useState(initialValue) {
value = value || initialValue
function setState(newVal) {
value = newVal
}
return [value, setState]
}
}
})()
Таким образом, при каждом его выполнении значение может быть сохранено в виде замыкания.
Но это все еще не соответствует реагированию в тельце. Поскольку на практике будут несколько звонков, как следует.
function App() {
const [name, setName] = useState('Kevin');
const [age, setAge] = useState(0);
const handleName = () => {
setNum('Dom');
};
const handleAge = () => {
setAge(age + 1);
};
return (
<div>
<p>姓名: {name}</p>
<button onClick={handleName}> 改名字 </button>
<p>年龄: {age}</p>
<button onClick={handleAge}> 加一岁 </button>
</div>
);
}
Поэтому нам нужно изменить способ, которым у тиференциализации хранится государство
фиктивная реализация useState
const MyReact = (function() {
// 开辟一个储存 hooks 的空间
let hooks = [];
// 指针从 0 开始
let currentHook = 0
return {
// 伪代码 解释重新渲染的时候 会初始化 currentHook
render(Component) {
const Comp = Component()
Comp.render()
currentHook = 0 // 重新渲染时候改变 hooks 指针
return Comp
},
useState(initialValue) {
hooks[currentHook] = hooks[currentHook] || initialValue
const setStateHookIndex = currentHook
// 这里我们暂且默认 setState 方式第一个参数不传 函数,直接传状态
const setState = newState => (hooks[setStateHookIndex] = newState)
return [hooks[currentHook++], setState]
}
}
})()
Таким образом, при повторном рендеринге приложения параметр kevin, 0, переданный при повторном выполнении useState, не будет использоваться, а будет напрямую использоваться значение, сохраненное предыдущими хуками.
правило крючков
В правиле hoos на официальном сайте четко указано, что хуки не должны использоваться в циклах, условиях или вложенных функциях.
почему бы нет?
Давайте взглянем
Следующий фрагмент кода. Выполните useState для повторного рендеринга, который отличается от исходной последовательности рендеринга, и возникнут следующие проблемы.
Если вы понимаете метод хранения описанного выше метода записи моделирования useState, то причина этой проблемы будет легко решена.
парсинг useEffect
использованиеЭффект
Инициализация напечатает 'useEffect_execute' один раз, изменит возраст и перерендерит, он напечатает снова, изменит имя и перерендерит, но не напечатает. Поскольку значение возраста отслеживается в массиве зависимостей
import React, { useState, useEffect } from 'react';
function App() {
const [name, setName] = useState('Kevin');
const [age, setAge] = useState(0);
const handleName = () => {
setName('Don');
};
const handleAge = () => {
setAge(age + 1);
};
useEffect(()=>{
console.log('useEffect_execute')
}, [age])
return (
<div>
<p>姓名: {name}</p>
<button onClick={handleName}> 改名字 </button>
<p>年龄: {age}</p>
<button onClick={handleAge}> 加一岁 </button>
</div>
);
}
export default App;
Макетная реализация useEffect
const MyReact = (function() {
// 开辟一个储存 hooks 的空间
let hooks = [];
// 指针从 0 开始
let currentHook = 0 ;
// 定义个模块全局的 useEffect 依赖
let deps;
return {
// 伪代码 解释重新渲染的时候 会初始化 currentHook
render(Component) {
const Comp = Component()
Comp.render()
currentHook = 0 // 重新渲染时候改变 hooks 指针
return Comp
},
useState(initialValue) {
hooks[currentHook] = hooks[currentHook] || initialValue
const setStateHookIndex = currentHook
// 这里我们暂且默认 setState 方式第一个参数不传 函数,直接传状态
const setState = newState => (hooks[setStateHookIndex] = newState)
return [hooks[currentHook++], setState]
}
useEffect(callback, depArray) {
const hasNoDeps = !depArray
// 如果没有依赖,说明是第一次渲染,或者是没有传入依赖参数,那么就 为 true
// 有依赖 使用 every 遍历依赖的状态是否变化, 变化就会 true
const hasChangedDeps = deps ? !depArray.every((el, i) => el === deps[i]) : true
// 如果没有依赖, 或者依赖改变
if (hasNoDeps || hasChangedDeps) {
// 执行
callback()
// 更新依赖
deps = depArray
}
},
}
})()
Меры предосторожности
Зависимости должны быть реальными
Зависимости необходимо выяснить.
Когда я впервые начал использовать useEffect, я устанавливал зависимости только тогда, когда хотел повторно запустить useEffect.
Тогда возникнут следующие проблемы.
Желаемый эффект заключается в том, что интерфейс увеличивается на один год в секунду
import React, { useState, useEffect } from 'react';
function App() {
const [name, setName] = useState('Kevin');
const [age, setAge] = useState(0);
const handleName = () => {
setName('Don');
};
const handleAge = () => {
setAge(age + 1);
};
useEffect(() => {
setInterval(() => {
setAge(age + 1);
console.log(age)
}, 1000);
}, []);
return (
<div>
<p>姓名: {name}</p>
<button onClick={handleName}> 改名字 </button>
<p>年龄: {age}</p>
<button onClick={handleAge}> 加一岁 </button>
</div>
);
}
export default App;
Фактически, вы обнаружите, что интерфейс прибавил возраст. Причина:
** В первом рендереage
да0
. следовательно,setAge(age+ 1)
в первом рендере эквивалентноsetAge(0 + 1)
. Однако я устанавливаю 0 зависимостей для пустого массива, тогда последующий useEffect не будет перезапускаться, он будет вызывать setAge(0 + 1) каждую секунду после него **
То есть, когда нам нужно зависеть от возраста, мы должны записывать его зависимости в массив зависимостей. Таким образом, useEffect будет работать нормально для нас.
Итак, если мы хотим увеличивать каждую секунду, есть два способа
метод первый:
Подлинность вы полагаетесь на состояние для заполнения массива
// 通过监听 age 的变化。来重新执行 useEffect 内的函数
// 因此这里也就需要记录定时器,当卸载的时候我们去清空定时器,防止多个定时器重新触发
useEffect(() => {
const id = setInterval(() => {
setAge(age + 1);
}, 1000);
return () => {
clearInterval(id)
};
}, [age]);
Способ второй
Параметр useState передается методу.
Примечание:UseState, который мы смоделировали выше, не выполняет эту обработку.Я объясню анализ в исходном коде позже.
useEffect(() => {
setInterval(() => {
setAge(age => age + 1);
}, 1000);
}, []);
useEffect запускается только один раз, ему больше не нужно знать текущийage
стоимость.因为 React render 的时候它会帮我们处理
это точноsetAge(age => age + 1)
Список задач. При повторном рендеринге он выполнит за нас этот метод и перейдет в самое последнее состояние.
Таким образом, нам удалось все время изменить состояние, но нам не нужно было писать эту зависимость в зависимости, потому что мы удалили исходную зависимость. (Это предложение кажется неправильным)
Проблема бесконечного запроса интерфейса
Когда я впервые начал использовать useEffect, я часто писал такой код при запросе интерфейса.
В реквизите есть номер страницы.Переключая номер страницы, я надеюсь следить за изменением номера страницы, чтобы снова запрашивать данные.
// 以下是伪代码
// 这里用 dva 发送请求来模拟
import React, { useState, useEffect } from 'react';
import { connect } from 'dva';
function App(props) {
const { goods, dispatch, page } = props;
useEffect(() => {
// 页面完成去发情请求
dispatch({
type: '/goods/list',
payload: {page, pageSize:10},
});
// xxxx
}, [props]);
return (
<div>
<p>商品: {goods}</p>
<button>点击切下一页</button>
</div>
);
}
export default connect(({ goods }) => ({
goods,
}))(App);
Потом с гордостью обновил интерфейс, и обнаружил в Сети сумасшедший зацикленный интерфейс запросов, из-за чего страница зависала.
Причина в том, что в зависимости мы изменили обновление свойств состояния через интерфейс, что вызвало повторную визуализацию компонента, что вызвало повторное выполнение метода в useEffect.После выполнения метода обновление из реквизита вызвали повторную визуализацию компонента.Зависимый элемент является объектом, ссылочные типы оказываются неравными, а метод в useEffect выполняется, повторно визуализируется, а затем сравнивается, не равен и выполняется снова . Отсюда бесконечный цикл.
Анализ исходного кода хуков
Расположение исходного кода: react/packages/react-reconciler/src/ReactFiberHooks.js
const Dispatcher={
useReducer: mountReducer,
useState: mountState,
// xxx 省略其他的方法
}
Исходный код mountState
function mountState<S>(
initialState: (() => S) | S,
): [S, Dispatch<BasicStateAction<S>>] {
/*
mountWorkInProgressHook 方法 返回初始化对象
{
memoizedState: null,
baseState: null,
queue: null,
baseUpdate: null,
next: null,
}
*/
const hook = mountWorkInProgressHook();
// 如果传入的是函数 直接执行,所以第一次这个参数是 undefined
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: (initialState: any),
});
/*
定义 dispatch 相当于
const dispatch = queue.dispatch =
dispatchAction.bind(null,currentlyRenderingFiber,queue);
*/
const dispatch: Dispatch<
BasicStateAction<S>,
> = (queue.dispatch = (dispatchAction.bind(
null,
// Flow doesn't know this is non-null, but we do.
((currentlyRenderingFiber: any): Fiber),
queue,
): any));
// 可以看到这个dispatch就是dispatchAction绑定了对应的 currentlyRenderingFiber 和 queue。最后return:
return [hook.memoizedState, dispatch];
}
Исходный код dispatchAction
function dispatchAction<A>(fiber: Fiber, queue: UpdateQueue<A>, action: A) {
//... 省略验证的代码
const alternate = fiber.alternate;
/*
这其实就是判断这个更新是否是在渲染过程中产生的,currentlyRenderingFiber只有在FunctionalComponent更新的过程中才会被设置,在离开更新的时候设置为null,所以只要存在并更产生更新的Fiber相等,说明这个更新是在当前渲染中产生的,则这是一次reRender。
所有更新过程中产生的更新记录在renderPhaseUpdates这个Map上,以每个Hook的queue为key。
对于不是更新过程中产生的更新,则直接在queue上执行操作就行了,注意在最后会发起一次scheduleWork的调度。
*/
if (
fiber === currentlyRenderingFiber ||
(alternate !== null && alternate === currentlyRenderingFiber)
) {
didScheduleRenderPhaseUpdate = true;
const update: Update<A> = {
expirationTime: renderExpirationTime,
action,
next: null,
};
if (renderPhaseUpdates === null) {
renderPhaseUpdates = new Map();
}
const firstRenderPhaseUpdate = renderPhaseUpdates.get(queue);
if (firstRenderPhaseUpdate === undefined) {
renderPhaseUpdates.set(queue, update);
} else {
// Append the update to the end of the list.
let lastRenderPhaseUpdate = firstRenderPhaseUpdate;
while (lastRenderPhaseUpdate.next !== null) {
lastRenderPhaseUpdate = lastRenderPhaseUpdate.next;
}
lastRenderPhaseUpdate.next = update;
}
} else {
const currentTime = requestCurrentTime();
const expirationTime = computeExpirationForFiber(currentTime, fiber);
const update: Update<A> = {
expirationTime,
action,
next: null,
};
flushPassiveEffects();
// Append the update to the end of the list.
const last = queue.last;
if (last === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
const first = last.next;
if (first !== null) {
// Still circular.
update.next = first;
}
last.next = update;
}
queue.last = update;
scheduleWork(fiber, expirationTime);
}
}
Исходный код mountReducer
Третий параметр Dole — это выполнение функции, начальное состояние по умолчанию не определено.
Другие аналогичны mountState выше.
function mountReducer<S, I, A>(
reducer: (S, A) => S,
initialArg: I,
init?: I => S,
): [S, Dispatch<A>] {
const hook = mountWorkInProgressHook();
let initialState;
if (init !== undefined) {
initialState = init(initialArg);
} else {
initialState = ((initialArg: any): S);
}
// 其他和 useState 一样
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
last: null,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: (initialState: any),
});
const dispatch: Dispatch<A> = (queue.dispatch = (dispatchAction.bind(
null,
// Flow doesn't know this is non-null, but we do.
((currentlyRenderingFiber: any): Fiber),
queue,
): any));
return [hook.memoizedState, dispatch];
}
Из исходного кода реакции видно, что useState — это специальный useReducer.
- видимый
useState
Но это синтаксический сахар, суть на самом делеuseReducer
- updateState повторно использует updateReducer (разница в том, что updateState устанавливает редьюсер на updateReducer)
- Хотя mountState напрямую не вызывает mountReducer, это почти то же самое (разница в том, что mountState устанавливает редюсер в basicStateReducer)
注:这里仅是 react 源码,至于重新渲染这块 react-dom 还没有去深入了解。
обновить:
Есть два случая,Это ререндеринг, так называемыйreRender
то естьЕсли в текущем цикле обновления генерируются новые обновления, продолжайте выполнять эти обновления до тех пор, пока в текущем цикле рендеринга не будет обновлений.
Их основные операции одинаковы, т.reducer
а такжеupdate.action
создать новыйstate
, и присвоитьHook.memoizedState
так же какHook.baseState
.
Обратите внимание, что для не-reRender
Если это необходимо, мы будем оценивать приоритет каждого обновления.Если обновление не находится в пределах текущего общего приоритета обновления, оно будет пропущено, а первое пропущенное будет пропущено.Update
станет новымbaseUpdate
,Он записывает все обновления после этого, даже если приоритет выше его, потому что при его выполнении необходимо следить за тем, чтобы последующие обновления выполнялись снова на основе его обновления, потому что результаты могут быть другими.
крючки в преакте
Лучшая альтернатива React с открытым исходным кодом для Preact! (облегченный 3kb)
Примечание. Замена здесь означает, что вы можете использовать это, если не используете реакцию. а не заменить.
Анализ исходного кода useState
Исходный код useReducer называется
export function useState(initialState) {
return useReducer(invokeOrReturn, initialState);
}
Анализ исходного кода useReducer
// 模块全局定义
/** @type {number} */
let currentIndex; // 状态的索引,也就是前面模拟实现 useState 时候所说的指针
let currentComponent; // 当前的组件
export function useReducer(reducer, initialState, init) {
/** @type {import('./internal').ReducerHookState} */
// 通过 getHookState 方法来获取 hooks
const hookState = getHookState(currentIndex++);
// 如果没有组件 也就是初始渲染
if (!hookState._component) {
hookState._component = currentComponent;
hookState._value = [
// 没有 init 执行 invokeOrReturn
// invokeOrReturn 方法判断 initialState 是否是函数
// 是函数 initialState(null) 因为初始化没有值默认为null
// 不是函数 直接返回 initialState
!init ? invokeOrReturn(null, initialState) : init(initialState),
action => {
// reducer == invokeOrReturn
const nextValue = reducer(hookState._value[0], action);
// 如果当前的值,不等于 下一个值
// 也就是更新的状态的值,不等于之前的状态的值
if (hookState._value[0]!==nextValue) {
// 储存最新的状态
hookState._value[0] = nextValue;
// 渲染组件
hookState._component.setState({});
}
}
];
}
// hookState._value 数据格式也就是 [satea:any, action:Function] 的数据格式拉
return hookState._value;
}
метод getHookState
function getHookState(index) {
if (options._hook) options._hook(currentComponent);
const hooks = currentComponent.__hooks || (currentComponent.__hooks = { _list: [], _pendingEffects: [], _pendingLayoutEffects: [] });
if (index >= hooks._list.length) {
hooks._list.push({});
}
return hooks._list[index];
}
метод invokeOrReturn
function invokeOrReturn(arg, f) {
return typeof f === 'function' ? f(arg) : f;
}
Суммировать
Пользуюсь крючками несколько месяцев. В основном все компоненты класса я использую для написания функциональных компонентов. Теперь многие компоненты реактивного сообщества также начали поддерживать хуки. Если вы знаете какой-то важный исходный код, вы можете знать, что это такое и зачем это нужно, то его использование в реальной работе может уменьшить количество ненужных ошибок и повысить эффективность.
наконец
Во всей статье, если есть какие-либо ошибки или неточности, обязательно исправьте их, спасибо!
Ссылаться на: