Введение
Две предыдущие статьи представилиreact-hooksКак использовать и настроитьhooksШаблоны проектирования и их реальная борьба, эта статья в основном изreact-hooksПроисхождение, принцип, угол исходного кода, начать анализироватьreact-hooksМеханизм работы и внутренние принципы, думаю, что после этой статьи, для тех, у кого брали интервьюhooksПроблема решена. действительныйreact-hooksЭто не так сложно понять, звучит оченьcool, который на самом деле является функциональным компонентомрешить нетstate, жизненный цикл, логика не может быть использована повторнотехническое решение.
Хуки появились в React 16.8 впервые. Он позволяет вам использовать состояние и другие функции React без написания классов.
Старые правила,🤔️🤔️🤔️Начнем сегодняшнюю дискуссию с вопросов (Могу ответить на несколько, можете попробовать сами, освоить степень):
- 1 Каждый раз, когда выполняется контекст функции компонента без состояния,
reactкаким образом это было записаноhooksстатус? - еще 2
react-hooksчто использовать для записи каждогоhooksв порядке ? Меняй вопрос! Почему условный оператор не может объявитьhooks?hooksПочему объявление находится в самом верху компонента? - 3
functionв функциональном компонентеuseState,иclassкомпонент классаsetStateКакая разница? - 4
reactкак это было захваченоhooksКонтекст выполнения находится внутри функционального компонента? - 5
useEffect,useMemo, ЗачемuseRefДоступ к последним измененным значениям без внедрения зависимостей? - 6
useMemoКак кэшируется значение? Как применить его для оптимизации производительности? - 7 Почему это передается дважды
useStateЗначение такое же, функциональный компонент не обновляется? - ...
Если вы внимательно прочитаете эту статью, все эти проблемы будут решены.
Существенная разница между функциональными компонентами и компонентами класса
объяснениеreact-hooksПеред принципом нам нужно углубить наше понимание,В чем разница между функциональными компонентами и компонентами класса?, без лишних слов, посмотрим
Два фрагмента кода.
class Index extends React.Component<any,any>{
constructor(props){
super(props)
this.state={
number:0
}
}
handerClick=()=>{
for(let i = 0 ;i<5;i++){
setTimeout(()=>{
this.setState({ number:this.state.number+1 })
console.log(this.state.number)
},1000)
}
}
render(){
return <div>
<button onClick={ this.handerClick } >num++</button>
</div>
}
}
распечатать результат?
Давайте снова посмотрим на функциональный компонент:
function Index(){
const [ num ,setNumber ] = React.useState(0)
const handerClick=()=>{
for(let i=0; i<5;i++ ){
setTimeout(() => {
setNumber(num+1)
console.log(num)
}, 1000)
}
}
return <button onClick={ handerClick } >{ num }</button>
}
распечатать результат?
------------ Опубликовать ответ -------------
В первом примере 🌰 выводит результат: 1 2 3 4 5
Во втором примере 🌰 выводит результат: 0 0 0 0 0
Эта проблема на самом деле очень запутанная. Давайте проанализируем ее вместе. В компоненте первого класса из-за исполненияsetStateне здесьreactвыполняется в обычном контексте выполнения функции, ноsetTimeoutказнен в г.Массовое обновлениеУсловия нарушены. Я не буду говорить здесь о принципе, поэтому вы можете напрямую получить измененныйstate.
Но в компонентах без гражданства это, похоже, не действует. Причина проста, вclassсостояние через созданный экземплярclass, чтобы поддерживать различные состояния в компоненте; но вfunctionВ компоненте нет состояния для сохранения этой информации, каждый раз при выполнении контекста функции все переменные и константы заново объявляются, выполняются, а затем перерабатываются механизмом мусора. Итак, как указано выше, независимоsetTimeoutСколько раз выполняется, выполняется в текущем контексте функции, в это времяnum = 0не изменится послеsetNumberВыполнить, после повторного выполнения функционального компонента,numизменять.
Таким образом, дляclassКомпонент, нам нужно создать его экземпляр только один раз, и экземпляр сохранитstateи т.п. статус. Для каждого обновления просто позвонитеrenderметод подойдет. Но когдаfunctionВ компоненте каждое обновление представляет собой выполнение новой функции, чтобы сохранить какое-то состояние, выполнить некоторые хуки побочных эффектов,react-hooksОн появился, чтобы помочь записывать состояние компонента и обрабатывать некоторые дополнительные побочные эффекты.
Первое знакомство: приоткройте завесу крючков
1 Что происходит, когда мы вводим хуки?
Мы импортируем изhooksначать сuseStateНапример, когда мы пишем это из проекта:
import { useState } from 'react'
Итак, мы пошли искатьuseState, чтобы увидеть, что это за фея?
react/src/ReactHooks.js
useState
export function useState(initialState){
const dispatcher = resolveDispatcher();
return dispatcher.useState(initialState);
}
useState()исполнение равноdispatcher.useState(initialState)Это вводитdispatcher, Давайте взглянемresolveDispatcherчто ты сделал?
resolveDispatcher
function resolveDispatcher() {
const dispatcher = ReactCurrentDispatcher.current
return dispatcher
}
ReactCurrentDispatcher
react/src/ReactCurrentDispatcher.js
const ReactCurrentDispatcher = {
current: null,
};
Мы виделиReactCurrentDispatcher.currentПри инициализации какnull, а потом нет текста. Пока можно поставить только **ReactCurrentDispatcher**записывать. ВзгляниReactCurrentDispatcherКогда он использовался?
2 Начать создание, начиная с выполнения функций компонентов без состояния
хочу полностью понятьhooks, необходимо начать с его источника, выше мы вводимhooks, наконец, сReactCurrentDispatcherПоспешное окончание, все подсказки сломаны, поэтому мы можем начать только с выполнения функционального компонента.
renderWithHooks выполнить функцию
заfunctionКогда выполняется компонент?
react-reconciler/src/ReactFiberBeginWork.js
functionИнициализация компонента:
renderWithHooks(
null, // current Fiber
workInProgress, // workInProgress Fiber
Component, // 函数组件本身
props, // props
context, // 上下文
renderExpirationTime,// 渲染 ExpirationTime
);
для инициализации нетcurrentПосле дерева, после завершения обновления компонента, будетworkInProgressприсвоение дереваcurrentДерево.
functionОбновление компонентов:
renderWithHooks(
current,
workInProgress,
render,
nextProps,
context,
renderExpirationTime,
);
Мы можем видеть с высоты,renderWithHooksфункцияперечислитьfunctionфункция компонентаосновная функция. Давайте сосредоточимся наrenderWithHooksчто ты сделал?
renderWithHooks
react-reconciler/src/ReactFiberHooks.js
export function renderWithHooks(
current,
workInProgress,
Component,
props,
secondArg,
nextRenderExpirationTime,
) {
renderExpirationTime = nextRenderExpirationTime;
currentlyRenderingFiber = workInProgress;
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.expirationTime = NoWork;
ReactCurrentDispatcher.current =
current === null || current.memoizedState === null
? HooksDispatcherOnMount
: HooksDispatcherOnUpdate;
let children = Component(props, secondArg);
if (workInProgress.expirationTime === renderExpirationTime) {
// ....这里的逻辑我们先放一放
}
ReactCurrentDispatcher.current = ContextOnlyDispatcher;
renderExpirationTime = NoWork;
currentlyRenderingFiber = null;
currentHook = null
workInProgressHook = null;
didScheduleRenderPhaseUpdate = false;
return children;
}
Все компоненты функции выполняются в этом методе, Прежде всего, мы должны понять несколько мыслей, которые важны для нашего последующего пониманияuseStateочень полезно.
current fiber树: когда рендеринг завершен,currentДерево,currentБудет вcommitЗамените сцену на настоящуюDomДерево.
workInProgress fiber树: вот-вот будет согласовано и отрендереноfiberДерево. Еще раз новый компонент обновляется сcurrentсделать копию какworkInProgress, после завершения обновления текущийworkInProgressприсвоение дереваcurrentДерево.
workInProgress.memoizedState: существуетclassкомпонент,memoizedStateхранитьstateинформация, вfunctionкомпонент,Здесь это может быть раскрыто заранее,memoizedStateВ процессе рендеринга согласования он сохраняется в виде связанного списка.hooksИнформация.
workInProgress.expirationTime: reactс разнымиexpirationTime, чтобы определить приоритет обновления.
currentHook: понятноcurrentтекущий запланированный указатель на деревеhooksузел.
workInProgressHook: понятноworkInProgressв настоящее время запланировано на дереве, указывающем наhooksузел.
renderWithHooksОсновная функция функции:
Сначала очистите согласованный рендерингworkInProgressдеревоmemoizedStateиupdateQueue, зачем это делать, ведь в следующем процессе выполнения функционального компонента новыйhooksИнформация монтируется на эти два свойства, а затем в компонентcommitсцена, воляworkInProgressзаменить дерево наcurrentдерево, заменить настоящимDOMузел элемента. И вcurrentдерево сохранитьhooksИнформация.
Затем в зависимости от того, визуализируется ли текущий компонент функции в первый раз, назначьтеReactCurrentDispatcher.currentразныеhooks, наконец, как упоминалось вышеReactCurrentDispatcherСвязаться. Для первого рендеринга компонента используйтеHooksDispatcherOnMountобъект крючков.
Для функциональных компонентов, которые необходимо обновить после рендеринга,HooksDispatcherOnUpdateобъект, то два различия передаются черезcurrentна деревеmemoizedState(крюк информации) судить. еслиcurrentНе существует, получается, что функциональный компонент визуализируется впервые.
Следующий,перечислитьComponent(props, secondArg);Выполнить наш функциональный компонент, наш функциональный компонент фактически выполняется здесь, а затем мы пишемhooksвыполняются последовательно, т.hooksИнформация сохраняется последовательно вworkInProgressна дереве.Что касается того, как он сохраняется, мы поговорим об этом в ближайшее время.
Далее, и это очень важно,ContextOnlyDispatcherназначить в ReactCurrentDispatcher.current,так какjsОн однопоточный, а это значит, что мы не в функциональном компоненте, вызывающемhooks, обаContextOnlyDispatcherна объектеhooks, ПосмотримContextOnlyDispatcherКрючки, что это?
const ContextOnlyDispatcher = {
useState:throwInvalidHookError
}
function throwInvalidHookError() {
invariant(
false,
'Invalid hook call. Hooks can only be called inside of the body of a function component. This could happen for' +
' one of the following reasons:\n' +
'1. You might have mismatching versions of React and the renderer (such as React DOM)\n' +
'2. You might be breaking the Rules of Hooks\n' +
'3. You might have more than one copy of React in the same app\n' +
'See https://fb.me/react-invalid-hook-call for tips about how to debug and fix this problem.',
);
}
Я понимаю,react-hooksИменно через этот функциональный компонент назначение выполняется иначе.hooksобъектный метод, оцениваемый вhooksНезависимо от того, находится ли выполнение внутри функционального компонента, исключение перехватывается и генерируется.
Наконец, сбросьте некоторые переменные, такие какcurrentHook,currentlyRenderingFiber,workInProgressHookЖдать.
3 разныхhooksобъект
Выше упоминалось, что когда функция сначала отображает компонент и обновляет компонент, соответственно вызываются разные функции.hooksобъект, мы сейчас увидимHooksDispatcherOnMountиHooksDispatcherOnUpdate.
Первый рендеринг (здесь я показываю только часто используемые)hooks):
const HooksDispatcherOnMount = {
useCallback: mountCallback,
useEffect: mountEffect,
useLayoutEffect: mountLayoutEffect,
useMemo: mountMemo,
useReducer: mountReducer,
useRef: mountRef,
useState: mountState,
};
Компоненты обновления:
const HooksDispatcherOnUpdate = {
useCallback: updateCallback,
useEffect: updateEffect,
useLayoutEffect: updateLayoutEffect,
useMemo: updateMemo,
useReducer: updateReducer,
useRef: updateRef,
useState: updateState
};
Похоже, что компонент визуализируется впервые, и обновленный компонент,react-hooksдва набораApi, вторая и третья части этой статьи будут посвящены связи между ними.
Мы используем блок-схему, чтобы описать весь процесс:
Инициализация трех хуков, как будут выглядеть хуки, которые мы пишем
В этой статье основное внимание будет уделено четырем средним точкам.hooksExpand, соответственно отвечающий за обновление компонентаuseState, ответственный за выполнение побочных эффектовuseEffect, ответственный за сохранение данныхuseRef, отвечающий за оптимизацию кешаuseMemo, что касаетсяuseCallback,useReducer,useLayoutEffectПринцип и четыре ключевых моментаhooksЭто относительно близко, поэтому я не буду объяснять это по одному.
Сначала мы пишем компонент и используем вышеприведенные четыре основныхhooks:
Пожалуйста, запомните следующие фрагменты кода, следующие пояснения будут дополнены следующими фрагментами кода.
import React , { useEffect , useState , useRef , useMemo } from 'react'
function Index(){
const [ number , setNumber ] = useState(0)
const DivDemo = useMemo(() => <div> hello , i am useMemo </div>,[])
const curRef = useRef(null)
useEffect(()=>{
console.log(curRef.current)
},[])
return <div ref={ curRef } >
hello,world { number }
{ DivDemo }
<button onClick={() => setNumber(number+1) } >number++</button>
</div>
}
Далее, давайте изучим четыре, которые мы написали выше.hooksЧем это закончится?
1 mountWorkInProgressHook
Когда компонент инициализируется, каждый разhooksвыполнить, какuseState(),useRef(), позвонюmountWorkInProgressHook,mountWorkInProgressHookЧто вы написали, давайте разберем вместе:
react-reconciler/src/ReactFiberHooks.js -> mountWorkInProgressHook
function mountWorkInProgressHook() {
const hook: Hook = {
memoizedState: null, // useState中 保存 state信息 | useEffect 中 保存着 effect 对象 | useMemo 中 保存的是缓存的值和deps | useRef中保存的是ref 对象
baseState: null,
baseQueue: null,
queue: null,
next: null,
};
if (workInProgressHook === null) { // 例子中的第一个`hooks`-> useState(0) 走的就是这样。
currentlyRenderingFiber.memoizedState = workInProgressHook = hook;
} else {
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
mountWorkInProgressHookТо, что делает эта функция, очень просто, сначала выполняйте по одной за раз.hooksфункции, оба производятhookобъект, который содержит текущийhookинформацию, а затем преобразовать каждуюhooksОбъединены в связанный список и назначеныworkInProgressизmemoizedState. Это также подтверждает сказанное выше, функциональный компонент используетmemoizedStateхранитьhooksсвязанный список.
Что касаетсяhookКакая информация сохраняется в объекте? Я представлю их отдельно здесь
:
memoizedState:useState中спастиstateИнформация |useEffectхранится вeffectОбъект |useMemoIn сохраняет кэшированное значение иdeps|useRefхранится вrefобъект.
baseQueue : usestateиuseReducerСредний Сохраняет последнюю очередь обновлений.
baseState:usestateиuseReducer, в обновлении, последний сгенерированныйstateценность.
queue: сохранить очередь для обновленияpendingQueue, функция обновленияdispatchи другая информация.
next: указать на следующийhooksобъект.
Затем, когда наш функциональный компонент выполняется, четыреhooksиworkInProgressбудет отношение, как показано.
знать каждыйhooksПосле отношений мы должны понять, почему условный оператор не может объявитьhooks.
Мы используем диаграмму, чтобы показать, что происходит, если выражение сделано в условном выражении.
Если положить вышеуказанноеdemoодин из нихuseRefв условное выражение,
let curRef = null
if(isFisrt){
curRef = useRef(null)
}
потому что однажды объявленный в условном выраженииhooks, при следующем обновлении функционального компонента,hooksСтруктура связанного списка будет уничтожена,currentдеревоmemoizedStateтайникhooksинформация и текущиеworkInProgressНепоследовательный, если он включает в себя чтениеstateДождитесь операции, произойдет исключение.
описано вышеhooksчем доказывать единственность, ответ, поhooksПорядок списка. и почему нельзя в условном выражении объявитьhooks, а затем мы следуем четырем направлениям, чтобы представить, что произошло во время инициализации?
2 Инициализировать useState -> mountState
mountState
function mountState(
initialState
){
const hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
// 如果 useState 第一个参数为函数,执行函数得到state
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
const queue = (hook.queue = {
pending: null, // 带更新的
dispatch: null, // 负责更新函数
lastRenderedReducer: basicStateReducer, //用于得到最新的 state ,
lastRenderedState: initialState, // 最后一次得到的 state
});
const dispatch = (queue.dispatch = (dispatchAction.bind( // 负责更新的函数
null,
currentlyRenderingFiber,
queue,
)))
return [hook.memoizedState, dispatch];
}
mountStateЧто сделано в итоге, он будет инициализирован первымstate, назначьте егоmountWorkInProgressHookпроизведеноhookобъектmemoizedStateиbaseStateсвойства, затем создайтеqueueОбъект, содержащий информацию, отвечающую за обновление.
Позвольте мне сначала сказать, что в компонентах без состоянияuseStateиuseReducerМетод запуска обновления функции:dispatchAction,useState, который можно рассматривать как упрощенную версиюuseReducer, что касаетсяdispatchActionкак обновитьstate, чтобы обновить компоненты, мы будем продолжать изучатьdispatchAction.
перед исследованием мысначала понятьdispatchActionчто это?
function dispatchAction<S, A>(
fiber: Fiber,
queue: UpdateQueue<S, A>,
action: A,
)
const [ number , setNumber ] = useState(0)
dispatchActionэтоsetNumber , dispatchActionПервый параметр и второй параметр былиbindизменить его наcurrentlyRenderingFiberиqueue, параметр, который мы передаем, является третьим параметромaction
Механизм обновления без сохранения состояния компонента dispatchAction
В качестве основной функции обновления, давайте изучим, ставлюdispatchActionСтримлайн, Стримлайн, Стримлайн,
function dispatchAction(fiber, queue, action) {
// 计算 expirationTime 过程略过。
/* 创建一个update */
const update= {
expirationTime,
suspenseConfig,
action,
eagerReducer: null,
eagerState: null,
next: null,
}
/* 把创建的update */
const pending = queue.pending;
if (pending === null) { // 证明第一次更新
update.next = update;
} else { // 不是第一次更新
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
const alternate = fiber.alternate;
/* 判断当前是否在渲染阶段 */
if ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber)) {
didScheduleRenderPhaseUpdate = true;
update.expirationTime = renderExpirationTime;
currentlyRenderingFiber.expirationTime = renderExpirationTime;
} else { /* 当前函数组件对应fiber没有处于调和渲染阶段 ,那么获取最新state , 执行更新 */
if (fiber.expirationTime === NoWork && (alternate === null || alternate.expirationTime === NoWork)) {
const lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
let prevDispatcher;
try {
const currentState = queue.lastRenderedState; /* 上一次的state */
const eagerState = lastRenderedReducer(currentState, action); /**/
update.eagerReducer = lastRenderedReducer;
update.eagerState = eagerState;
if (is(eagerState, currentState)) {
return
}
}
}
}
scheduleUpdateOnFiber(fiber, expirationTime);
}
}
Либо компонент класса вызываетsetStateили функциональный компонентdispatchAction, произведетupdateобъект, который записывает информацию об этом обновлении, а затем этотupdateвставить для обновленияpendingв очереди,dispatchActionВторой шаг – определить текущую функциональную составляющую.fiberНаходится ли объект в стадии рендеринга, если он в стадии рендеринга, то нам не нужно обновлять текущий функциональный компонент, достаточно обновить текущийupdateизexpirationTimeВот и все.
если текущийfiberНе на этапе обновления. затем позвонивlastRenderedReducerПолучить последнююstate, и предыдущийcurrentState, сделать поверхностное сравнение, если равно, то выйти, что подтверждает, почемуuseState, когда два значения равны, причина, по которой компонент не отображается, этот механизм иComponentв режимеsetStateЕсть определенные отличия.
если дваждыstateне равны, то вызовитеscheduleUpdateOnFiberтекущий график рендерингаfiber,scheduleUpdateOnFiberдаreactОсновная функция для рендеринга обновлений.
мы кладеминициализацияmountStateиМеханизм обновления компонентов без сохранения состоянияЭто понятно, давайте посмотрим на другойhooksЧто делает инициализация?
3 Инициализировать useEffect -> mountEffect
Вышеупомянутые компоненты без гражданстваfiberобъектmemoizedStateсохранить текущийhooksсформированный связанный список. ТакupdateQueueКакая информация сохраняется, мы рассмотрим далееuseEffectУзнайте в процессе.
когда мы звонимuseEffectКогда компонент визуализируется в первый раз, он вызываетсяmountEffectметод, что делает этот метод?
mountEffect
function mountEffect(
create,
deps,
) {
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
hook.memoizedState = pushEffect(
HookHasEffect | hookEffectTag,
create, // useEffect 第一次参数,就是副作用函数
undefined,
nextDeps, // useEffect 第二次参数,deps
);
}
каждыйhooksИнициализация создастhookобъект, а затем зацепитеmemoizedStateсохранить текущийeffect hookИнформация.
есть дваmemoizedStateНе запутайтесь, я вам еще раз напомню здесь
-
workInProgress / currentна деревеmemoizedStateСохраняется каждый из текущих функциональных компонентов.hooksсформированный связанный список. -
каждый
hooksВверхmemoizedStateсохранить текущийhooksинформация, различныеhooksизmemoizedStateКонтент разный. Вышеупомянутый метод, наконец, выполняетpushEffect, Давайте взглянемpushEffectчто ты сделал?
pushEffect создает объект эффекта и монтирует updateQueue
function pushEffect(tag, create, destroy, deps) {
const effect = {
tag,
create,
destroy,
deps,
next: null,
};
let componentUpdateQueue = currentlyRenderingFiber.updateQueue
if (componentUpdateQueue === null) { // 如果是第一个 useEffect
componentUpdateQueue = { lastEffect: null }
currentlyRenderingFiber.updateQueue = componentUpdateQueue
componentUpdateQueue.lastEffect = effect.next = effect;
} else { // 存在多个effect
const lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
const firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
Этот раздел на самом деле очень прост, сначала создайтеeffect, чтобы определить, визуализируется ли компонент в первый раз, затем создайтеcomponentUpdateQueue,этоworkInProgressизupdateQueue. потомeffectположить вupdateQueueсередина.
Предположим, мы пишем это в функциональном компоненте:
useEffect(()=>{
console.log(1)
},[ props.a ])
useEffect(()=>{
console.log(2)
},[])
useEffect(()=>{
console.log(3)
},[])
НаконецworkInProgress.updateQueueбудет сохранено так:
Расширение: список эффектов
effect listМожно понимать как хранилищеeffectTagКонтейнер со списком побочных эффектов. это изfiberУзлы и указателиnextEffectСформирована структура односвязного списка, в которую также входит первый узелfirstEffect, и последний узелlastEffect.ReactИспользуя алгоритм поиска в глубину,renderобход сценыfiberдерево, положить каждый побочный эффектfiberОтфильтруйте и, наконец, создайте, чтобы генерировать только побочный эффектeffect listсвязанный список.
существуетcommitсцена,Reactполучитьeffect listПосле данных, путем обходаeffect list, и по каждомуeffectузлаeffectTagвведите, выполните каждыйeffect, так что соответствующийDOMдерево выполняет изменения.
4 Инициализировать useMemo -> mountMemo
Я не знаю, все лиuseMemoЭто слишком сложно представить, по сравнению с другимиuseState , useEffectПодождите, его логика на самом деле довольно проста.
function mountMemo(nextCreate,deps){
const hook = mountWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
инициализацияuseMemo, заключается в созданииhook, затем выполнитеuseMemoПервый параметр , получите значение, которое необходимо кэшировать, а затем объедините значение сdepsЗапишите его и назначьте текущемуhookизmemoizedState. В общем, сложной логики нет.
5 Инициализировать useRef -> mountRef
заuseRefОбработка инициализации кажется проще, давайте посмотрим:
function mountRef(initialValue) {
const hook = mountWorkInProgressHook();
const ref = {current: initialValue};
hook.memoizedState = ref;
return ref;
}
mountRefИнициализация очень проста, создайте объект ref, объектcurrentсвойство для сохранения инициализированного значения и, наконец, использоватьmemoizedStateспастиref, завершает всю операцию.
Резюме 6 установленных сценических крючков
Подведем итоги этапа инициализации,react-hooksДелайте что-то во время первого контекста выполнения рендеринга функционального компонента, каждыйreact-hooksисполнение, произведетhookобъекта и образуют структуру связанного списка, связанную вworkInProgressизmemoizedStateатрибут, тоreact-hooksсостояние включено, привязано к текущемуhooksобъектmemoizedStateхарактеристики. заeffectКрючок с побочным эффектом, который будет связанworkInProgress.updateQueueвверх, подожди, покаcommitсцена,domПостроение дерева завершено, после каждого выполненияeffectКрючок с побочным эффектом.
Фаза обновления четырех хуков
Выше описан первый компонент функции рендеринга,react-hooksЧто делает инициализация, далее разберем,
Для этапа обновления укажите последний разworkInProgressдерево было присвоеноcurrentДерево. хранитьhooksинформационныйmemoizedState, который уже существуетcurrentна дереве,reactзаhooksлогика обработки иfiberЛогика дерева похожа.
Для обновления функционального компонента при повторном выполненииhooksфункция, такая какuseState(0), сначала изcurrentизhooksнайдено в нынешнемworkInProgressHook,соответствующийcurrentHooks, затем сделайте копиюcurrentHooksдаватьworkInProgressHook, следующийhooksКогда функция выполняется, обновите последнее состояние доworkInProgressHook,гарантироватьhooksСостояние не потеряно.
Таким образом, функциональный компонент обновляется каждый раз, каждый разreact-hooksДля выполнения функции требуется функция для выполнения вышеуказанных операций.updateWorkInProgressHook, мы рассмотрим это далееupdateWorkInProgressHook.
1 updateWorkInProgressHook
function updateWorkInProgressHook() {
let nextCurrentHook;
if (currentHook === null) { /* 如果 currentHook = null 证明它是第一个hooks */
const current = currentlyRenderingFiber.alternate;
if (current !== null) {
nextCurrentHook = current.memoizedState;
} else {
nextCurrentHook = null;
}
} else { /* 不是第一个hooks,那么指向下一个 hooks */
nextCurrentHook = currentHook.next;
}
let nextWorkInProgressHook
if (workInProgressHook === null) { //第一次执行hooks
// 这里应该注意一下,当函数组件更新也是调用 renderWithHooks ,memoizedState属性是置空的
nextWorkInProgressHook = currentlyRenderingFiber.memoizedState;
} else {
nextWorkInProgressHook = workInProgressHook.next;
}
if (nextWorkInProgressHook !== null) {
/* 这个情况说明 renderWithHooks 执行 过程发生多次函数组件的执行 ,我们暂时先不考虑 */
workInProgressHook = nextWorkInProgressHook;
nextWorkInProgressHook = workInProgressHook.next;
currentHook = nextCurrentHook;
} else {
invariant(
nextCurrentHook !== null,
'Rendered more hooks than during the previous render.',
);
currentHook = nextCurrentHook;
const newHook = { //创建一个新的hook
memoizedState: currentHook.memoizedState,
baseState: currentHook.baseState,
baseQueue: currentHook.baseQueue,
queue: currentHook.queue,
next: null,
};
if (workInProgressHook === null) { // 如果是第一个hooks
currentlyRenderingFiber.memoizedState = workInProgressHook = newHook;
} else { // 重新更新 hook
workInProgressHook = workInProgressHook.next = newHook;
}
}
return workInProgressHook;
}
Логика этого раздела примерно следующая:
- Сначала, если это первое исполнение
hooksфункцию, то изcurrentСнять с дереваmemoizedState, то есть старыйhooks. - затем объявите переменную
nextWorkInProgressHook, здесь следует отметить, что при нормальных обстоятельствах, как толькоrenderWithHooksвоплощать в жизнь,workInProgressВверхmemoizedStateостанется пустым,hooksфункции выполняются последовательно,nextWorkInProgressHookдолжны были бытьnull, то при каких обстоятельствахnextWorkInProgressHookне дляnull, то есть когда однаждыrenderWithHooksВ процессе выполнения функциональный компонент выполняется несколько раз, т.renderWithHooksв этой логике.
if (workInProgress.expirationTime === renderExpirationTime) {
// ....这里的逻辑我们先放一放
}
Логика здесь на самом деле состоит в том, чтобы определить, что если текущий функциональный компонент все еще находится в приоритете рендеринга после выполнения текущего функционального компонента, что указывает на то, что у функционального компонента есть новая задача обновления, тогда функциональный компонент выполняется в цикле. Это приводит к вышеизложенному,nextWorkInProgressHookне дляnullСлучай.
- последняя копия
currentизhooks, назначьте егоworkInProgressHook, используется для обновления нового раундаhooksгосударство.
Далее мы рассмотрим четыре типаhooks, в обновлении компонента выполните эти операции соответственно.
2 updateState
useState
function updateReducer(
reducer,
initialArg,
init,
){
const hook = updateWorkInProgressHook();
const queue = hook.queue;
queue.lastRenderedReducer = reducer;
const current = currentHook;
let baseQueue = current.baseQueue;
const pendingQueue = queue.pending;
if (pendingQueue !== null) {
// 这里省略... 第一步:将 pending queue 合并到 basequeue
}
if (baseQueue !== null) {
const first = baseQueue.next;
let newState = current.baseState;
let newBaseState = null;
let newBaseQueueFirst = null;
let newBaseQueueLast = null;
let update = first;
do {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime < renderExpirationTime) { //优先级不足
const clone = {
expirationTime: update.expirationTime,
...
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone;
newBaseState = newState;
} else {
newBaseQueueLast = newBaseQueueLast.next = clone;
}
} else { //此更新确实具有足够的优先级。
if (newBaseQueueLast !== null) {
const clone= {
expirationTime: Sync,
...
};
newBaseQueueLast = newBaseQueueLast.next = clone;
}
/* 得到新的 state */
newState = reducer(newState, action);
}
update = update.next;
} while (update !== null && update !== first);
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = newBaseQueueFirst;
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
const dispatch = queue.dispatch
return [hook.memoizedState, dispatch];
}
Этот абзац выглядит очень сложным, давайте потихоньку разбираться, в первую очередь последнее обновлениеpending queueОбъединить вbasequeue, зачем вы хотите это сделать, например пишем это в событии клика еще раз,
function Index(){
const [ number ,setNumber ] = useState(0)
const handerClick = ()=>{
// setNumber(1)
// setNumber(2)
// setNumber(3)
setNumber(state=>state+1)
// 获取上次 state = 1
setNumber(state=>state+1)
// 获取上次 state = 2
setNumber(state=>state+1)
}
console.log(number) // 3
return <div>
<div>{ number }</div>
<button onClick={ ()=> handerClick() } >点击</button>
</div>
}
Нажмите кнопку, чтобы распечатать 3
три разаsetNumberпроизведеноupdateвременно вставлюpending queue, когда выполняется следующий компонент функции, три разаupdateбыл объединен вbaseQueue. Структура выглядит следующим образом:
Далее будет текущийuseStateилиuseReduerсоответствующийhooksВверхbaseStateиbaseQueueОбновить до последнего статуса. будет циклbaseQueueизupdate, сделать копиюupdate,продлитьexpirationTime, для достаточного приоритетаupdate(вышеуказанные триsetNumberпроизведеноupdateимеют достаточный приоритет), мы хотим получить последнююstateгосударство. , выполнится один разuseStateна каждомaction. получить последнююstate.
состояние обновления
Тут два вопроса🤔️:
- Вопрос 1: Это не последняя казнь
actionНе может быть?
Ответ: Причина очень проста, как упоминалось вышеuseStateлогическая суммаuseReducerпочти. Если первый параметр является функцией, он будет ссылаться на последнийupdateпроизведеноstate, поэтому необходимоциклические вызовы, каждыйupdateизreducer,еслиsetNumber(2)Это так, тогда просто обновите значение, если оноsetNumber(state=>state+1), затем пройти в последнийstateполучить последнююstate.
- Вопрос 2: При каких обстоятельствах будет недостаточный приоритет (
updateExpirationTime < renderExpirationTime)?
Ответ: Такая ситуация обычно возникает, когда мы вызываемsetNumberпри звонкеscheduleUpdateOnFiberКогда текущий компонент визуализируется, генерируется новое обновление, поэтому окончательное выполнениеreducerвозобновитьstateЗадача передана на следующее обновление.
3 updateEffect
function updateEffect(create, deps): void {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps;
let destroy = undefined;
if (currentHook !== null) {
const prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
const prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {
pushEffect(hookEffectTag, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber.effectTag |= fiberEffectTag
hook.memoizedState = pushEffect(
HookHasEffect | hookEffectTag,
create,
destroy,
nextDeps,
);
}
useEffectДелать это просто, суди дваждыdepsРавно, если равно, это обновление не нужно выполнять, тогда вызывайте напрямуюpushEffect, обратите внимание здесьeffectТег,hookEffectTag, если не равно, то обновитьeffect, и присвоитьhook.memoizedState, где меткаHookHasEffect | hookEffectTag, затем вcommitсцена,reactПо метке будет судить, выполнять ли текущуюeffectфункция.
4 updateMemo
function updateMemo(
nextCreate,
deps,
) {
const hook = updateWorkInProgressHook();
const nextDeps = deps === undefined ? null : deps; // 新的 deps 值
const prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
const prevDeps = prevState[1]; // 之前保存的 deps 值
if (areHookInputsEqual(nextDeps, prevDeps)) { //判断两次 deps 值
return prevState[0];
}
}
}
const nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
Во время обновления компонента мы выполняемuseMemoФункция, что она делает, на самом деле очень проста, то есть судить дваждыdepsРавно ли, если не хотите ждать, это доказывает, что зависимости изменились, тогда выполняйтеuseMemoПервая функция получает новое значение, а затем переназначает егоhook.memoizedState, если равенство доказывает, что никакие зависимости не изменились, то получить кэшированное значение напрямую.
Но вот что стоит отметить,nextCreate()Выполнить, если на него есть ссылка внутриusestateи другая информация, переменная будет упоминаться и не может быть переработана механизмом сборки мусора, что является принципом закрытия, тогда доступный атрибут может не быть последним значением, поэтому ссылочное значение необходимо добавить в зависимостьdepв массиве. каждый разdepМеняйте, переделывайте, проблем не будет.
Теплый совет: многие студенты говорят, чтоuseMemoКак его использовать, в каких сценариях и не будет ли это контрпродуктивно?Проанализировав принцип работы исходного кода, могу однозначно сказать, что его в принципе можно использовать с уверенностью.Грубо говоря можно настроить кеш и сохранять и извлекать значения.
5 updateRef
function updateRef(initialValue){
const hook = updateWorkInProgressHook()
return hook.memoizedState
}
То, что делает компонент-функция для обновления useRef, проще, то есть возвращает закешированное значение, то есть неважно, как выполняется компонент-функция, сколько раз он выполняется,hook.memoizedStateВсе указывает на объект в памяти, так объясненоuseEffect,useMemo, ЗачемuseRefВы можете получить доступ к последним измененным значениям без внедрения зависимостей.
Обновление событий в один клик
5 Резюме
Выше мы начинаем сИнициализация функционального компонента,прибытьРендеринг обновления функционального компонента, двумерное разложение объясняетreact-hooksПринцип, освоилreact-hooksПринцип и внутренний механизм работы помогают нам лучше использовать его в нашей работе.react-hooks.
Напоследок подарите розы и оставьте в руках стойкий аромат.Друзья, которые чувствуют, что что-то приобрели, могут подарить авторуНравится, следуйОдна волна, обновляйте фронтальные суперхардкорные статьи одну за другой.
Резюме хороших статей в React
Два других в трилогии о реактивных крючках
-
Играйте с реактивными хуками, пользовательскими шаблонами дизайна хуков и их реальной борьбой
205+👍Нравится -
Как использовать реагирующие хуки
120+Нравится👍
реагировать продвинутая серия
-
Восемь предложений по оптимизации для разработчиков React в конце года «React Advanced»
880+Нравится👍 -
В статье «React Advanced» подробно рассматриваются компоненты React High-Order Components (HOC).
300+Нравится👍
серия исходников реакции
-
"Анализ исходного кода" На этот раз я полностью понимаю принцип маршрутизации react-router
120+Нравится👍
Серия проектов с открытым исходным кодом
-
«Страница кеша React» от спроса до открытого исходного кода (как я впечатлил даму продукта)
300+Нравится👍 -
"Фронтенд-инжиниринг" строит реакцию, строительные леса от 0 до 1 (суперподробное руководство на 1,2 Вт в слове)
300+Нравится👍