網(wǎng)站首頁(yè) 編程語言 正文
引言
之前我們介紹了beginWork,react使用的是深度優(yōu)先遍歷算法,整個(gè)fiber的構(gòu)建都遵循此算法。
這也意味著,并不是所有節(jié)點(diǎn)beginWork完成后,才去進(jìn)行completeWork。
當(dāng)beginWork的next為null時(shí),將進(jìn)去completeWork。
一. completeUnitOfWork
function completeUnitOfWork(unitOfWork) {
var completedWork = unitOfWork;
do {
// ...
next = completeWork(current, completedWork, subtreeRenderLanes);
// ...
if (returnFiber !== null && (returnFiber.flags & Incomplete) === NoFlags) {
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = completedWork.firstEffect;
}
if (completedWork.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork.firstEffect;
}
returnFiber.lastEffect = completedWork.lastEffect;
}
var flags = completedWork.flags;
if (flags > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = completedWork;
}else {
returnFiber.firstEffect = completedWork;
}
returnFiber.lastEffect = completedWork;
}
}
var siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
workInProgress = siblingFiber;
return;
}
completedWork = returnFiber;
workInProgress = completedWork;
} while (completedWork !== null);
// ...
}
根據(jù)深度優(yōu)先算法,當(dāng)beginWork完成其中某個(gè)子樹干的最后一個(gè)節(jié)點(diǎn)時(shí),進(jìn)入completeUnitOfWork。根據(jù)最后的這個(gè)節(jié)點(diǎn)先完成completeWork,依次往上,直到找到相鄰節(jié)點(diǎn)。
核心方法分為2部分,其一:completeWork,其二:完成effect掛載,并串聯(lián)所有節(jié)點(diǎn)的effect(包括節(jié)點(diǎn)內(nèi)部的effect)組裝成環(huán)狀鏈表。
二. completeWork
function completeWork(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
return null;
case ClassComponent:
// ...
case HostRoot:
// ...
updateHostContainer(workInProgress);
return null;
case HostComponent:
// ...
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
// ...
// ...
}
這里很神奇的是,在react內(nèi)部updateHostContainer居然是個(gè)空函數(shù),也許后續(xù)版本做do something吧。 普通節(jié)點(diǎn)將進(jìn)入HostComponent。
createInstance
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var parentNamespace;
{
// TODO: take namespace into account when validating.
var hostContextDev = hostContext;
validateDOMNesting(type, null, hostContextDev.ancestorInfo);
if (typeof props.children === 'string' || typeof props.children === 'number') {
var string = '' + props.children;
var ownAncestorInfo = updatedAncestorInfo(hostContextDev.ancestorInfo, type);
validateDOMNesting(null, string, ownAncestorInfo);
}
parentNamespace = hostContextDev.namespace;
}
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
}
createElement
function createElement(type, props, rootContainerElement, parentNamespace) {
// script標(biāo)簽處理...
// ...
// flow相關(guān)webComponents處理
// ...
domElement = ownerDocument.createElement(type);
// select標(biāo)簽特殊處理,單獨(dú)設(shè)置multiple以及size
// 非法標(biāo)簽告警處理...
return domElement;
}
到這里,我們可以看到開始創(chuàng)建每個(gè)fiber節(jié)點(diǎn)對(duì)應(yīng)的dom對(duì)象了。但是并沒有插入到文檔流中。 那么真實(shí)的dom是如何連接到fiber對(duì)象呢?
precacheFiberNode,會(huì)在每個(gè)真實(shí)dom對(duì)象下,掛載對(duì)應(yīng)的節(jié)點(diǎn)的fiber,precache是以_reactFiber$+隨機(jī)數(shù)的屬性。
updateFiberProps,會(huì)在每個(gè)真實(shí)dom對(duì)象下,掛載對(duì)應(yīng)的props children即element對(duì)象,以_reactProps$+隨機(jī)數(shù)。
問題來了:每個(gè)fiber對(duì)應(yīng)的真實(shí)dom對(duì)象,是單個(gè)構(gòu)建單個(gè)存儲(chǔ)?還是構(gòu)建一個(gè)總的dom樹?這之間是如何關(guān)聯(lián)起來的?
appendAllChildren
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
var node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) ; else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
這里已經(jīng)很明顯了,根據(jù)之前創(chuàng)建的fiber鏈表,循環(huán)node節(jié)點(diǎn),普通節(jié)點(diǎn)將調(diào)用appendInitialChild。即使用:parentInstance.appendChild(child); 至此可以看到循環(huán)結(jié)束后,生成了整個(gè)待插入的DOM節(jié)點(diǎn)(頁(yè)面首次渲染時(shí))。
另外需要注意的是,根據(jù)fiber關(guān)聯(lián)dom也是在這個(gè)階段進(jìn)行的(不是dom關(guān)聯(lián)fiber)
最后如何存在ref,當(dāng)前的fiber樹對(duì)應(yīng)的flags將和Ref的二進(jìn)制數(shù)據(jù)取位運(yùn)算或。(這很重要)
三. Effect
react推崇的是函數(shù)式編程,在一個(gè)函數(shù)組件里,如果存在useEffect等方法,那么react認(rèn)為這是一個(gè)副作用函數(shù)組件。那么這些副作用是如何組織起來,又是在什么階段運(yùn)行的呢?
早在beginWork,fiber的flags默認(rèn)都是二進(jìn)制0,如果存在副作用,如:useEffect,ref,useLayoutEffect等等,首次將被設(shè)置為Placement。但為什么存在useEffect函數(shù)組件的fiber對(duì)象,flags都是256以上的數(shù)值?
我們以u(píng)seEffect為例,一探究竟。
useEffect
function useEffect(create, deps) {
var dispatcher = resolveDispatcher();
return dispatcher.useEffect(create, deps);
}
沒錯(cuò),useEffect方法定義就兩行代碼。
resolveDispatcher
function resolveDispatcher() {
var dispatcher = ReactCurrentDispatcher.current;
if (!(dispatcher !== null)) {
{
throw Error( "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:\n1. You might have mismatching versions of React and the renderer (such as React DOM)\n2. You might be breaking the Rules of Hooks\n3. You might have more than one copy of React in the same app\nSee https://reactjs.org/link/invalid-hook-call for tips about how to debug and fix this problem." );
}
}
return dispatcher;
}
我們繼續(xù)看下ReactCurrentDispatcher的定義:
const ReactCurrentDispatcher = {
/**
* @internal
* @type {ReactComponent}
*/
current: (null: null | Dispatcher),
};
最早的ReactCurrentDispatcher,是在renderRoot階段。如果root或Lane被改變了,原來的dispatch可能被置空了或首次不存在,使用當(dāng)前的ContextOnlyDispatcher替代。
在函數(shù)組件beginWork階段,在執(zhí)行函數(shù)組件生成element對(duì)象之前,會(huì)賦值HooksDispatcherOnMount,這就是dispatch。
我們來看看HooksDispatcher:
{
readContext: function (context, observedBits) {
return readContext(context, observedBits);
},
useCallback: function (callback, deps) {
currentHookNameInDev = 'useCallback';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountCallback(callback, deps);
},
useContext: function (context, observedBits) {
currentHookNameInDev = 'useContext';
mountHookTypesDev();
return readContext(context, observedBits);
},
useEffect: function (create, deps) {
currentHookNameInDev = 'useEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountEffect(create, deps);
},
useImperativeHandle: function (ref, create, deps) {
currentHookNameInDev = 'useImperativeHandle';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountImperativeHandle(ref, create, deps);
},
useLayoutEffect: function (create, deps) {
currentHookNameInDev = 'useLayoutEffect';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
return mountLayoutEffect(create, deps);
},
useMemo: function (create, deps) {
currentHookNameInDev = 'useMemo';
mountHookTypesDev();
checkDepsAreArrayDev(deps);
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountMemo(create, deps);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
},
useReducer: function (reducer, initialArg, init) {
currentHookNameInDev = 'useReducer';
mountHookTypesDev();
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountReducer(reducer, initialArg, init);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
},
useRef: function (initialValue) {
currentHookNameInDev = 'useRef';
mountHookTypesDev();
return mountRef(initialValue);
},
useState: function (initialState) {
currentHookNameInDev = 'useState';
mountHookTypesDev();
var prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnMountInDEV;
try {
return mountState(initialState);
} finally {
ReactCurrentDispatcher$1.current = prevDispatcher;
}
},
useDebugValue: function (value, formatterFn) {
currentHookNameInDev = 'useDebugValue';
mountHookTypesDev();
return mountDebugValue();
},
useDeferredValue: function (value) {
currentHookNameInDev = 'useDeferredValue';
mountHookTypesDev();
return mountDeferredValue(value);
},
useTransition: function () {
currentHookNameInDev = 'useTransition';
mountHookTypesDev();
return mountTransition();
},
useMutableSource: function (source, getSnapshot, subscribe) {
currentHookNameInDev = 'useMutableSource';
mountHookTypesDev();
return mountMutableSource(source, getSnapshot, subscribe);
},
useOpaqueIdentifier: function () {
currentHookNameInDev = 'useOpaqueIdentifier';
mountHookTypesDev();
return mountOpaqueIdentifier();
},
unstable_isNewReconciler: enableNewReconciler
};
其中useEffect重點(diǎn)執(zhí)行mountEffect(create, deps)
function mountEffect(create, deps) {
{
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
warnIfNotCurrentlyActingEffectsInDEV(currentlyRenderingFiber$1);
}
}
return mountEffectImpl(Update | Passive, Passive$1, create, deps);
}
mountEffectImpl
function mountEffectImpl(fiberFlags, hookFlags, create, deps) {
var hook = mountWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
currentlyRenderingFiber$1.flags |= fiberFlags;
hook.memoizedState = pushEffect(HasEffect | hookFlags, create, undefined, nextDeps);
}
hook對(duì)象數(shù)據(jù)結(jié)構(gòu)如下:
{
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
問題來了:
- currentlyRenderingFiber是什么?和workInProgressFiber有什么關(guān)系?
- 多個(gè)函數(shù)組件和單個(gè)函數(shù)組件中多個(gè)Hook是如何關(guān)聯(lián)起來的?
- 整個(gè)hook和fiber怎么關(guān)聯(lián)起來的?
- 完整的hooks數(shù)據(jù)結(jié)構(gòu)又是什么?
currentlyRenderingFiber是當(dāng)前正在rendering階段的fiber對(duì)象,早在renderHook初始化階段賦值了workInProgressFiber。所以當(dāng)前函數(shù)組件的flags在這里被改變了,即有副作用的函數(shù)flags = flags | Update | Passive。
根據(jù)二進(jìn)制位運(yùn)算,根函數(shù)組件庫(kù)flags = 518,當(dāng)然這個(gè)數(shù)值也不是固定不變的,因?yàn)樽兓腷eginWork階段初始flags值。是根據(jù)不同的effects會(huì)有不同的初始值。
pushEffect
function pushEffect(tag, create, destroy, deps) {
var effect = {
tag: tag,
create: create,
destroy: destroy,
deps: deps,
// Circular
next: null
};
var componentUpdateQueue = currentlyRenderingFiber$1.updateQueue;
if (componentUpdateQueue === null) {
componentUpdateQueue = createFunctionComponentUpdateQueue();
currentlyRenderingFiber$1.updateQueue = componentUpdateQueue;
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
var lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
var firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
對(duì)于tag的值,是HasEffect和Passive按位運(yùn)算或的結(jié)果,實(shí)際上固定是5。
需要注意的是,函數(shù)組件的updateQueue和rootFiber的不一樣,以及普通節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)和作用也都不一樣。 函數(shù)組件的updateQueue關(guān)聯(lián)的是effect。這和render初始化階段rootFiber有巨大的差異。
上面的代碼很簡(jiǎn)單,每個(gè)函數(shù)組件如果存在多個(gè)effect,那么會(huì)將這些effect順序關(guān)聯(lián)起來,這個(gè)函數(shù)組件的fiberr.updateQueue對(duì)應(yīng)lastEffect,next即下一個(gè)effect,直到最后形成一個(gè)首尾相連的環(huán)狀鏈表結(jié)構(gòu)。
為什么是環(huán)狀?這個(gè)待到后續(xù)調(diào)度階段再解釋。
再思考一個(gè)問題:這里只是解決了單個(gè)組件內(nèi)的effect構(gòu)建,那么整個(gè)fiber鏈表里effect構(gòu)建是怎么樣的?執(zhí)行的順序又是什么?
四. rootFiber-Effect
在completedWork的最后,根據(jù)深度優(yōu)先遍歷算法,將每個(gè)節(jié)點(diǎn)的firstEffect層層往上傳遞,一直到rootFiber。而lastEffect也是層層往上判斷,直到上層最后一個(gè)effect,做為rootFiber的lastEffect。
每個(gè)fiber effect通過nextEffect鏈接起來,而fiber內(nèi)部通過updateQueue鏈接自身的effect環(huán)狀鏈表。
至此,completeWork階段就完成了,rootFiber以及各fiber節(jié)點(diǎn)大部分屬性都構(gòu)建完成了。
下一章,將進(jìn)入commit階段,更多關(guān)于React Fiber構(gòu)建completeWork的資料請(qǐng)關(guān)注AB教程網(wǎng)其它相關(guān)文章!
原文鏈接:https://juejin.cn/post/7196490153137094712
相關(guān)推薦
- 2022-06-30 Python函數(shù)和文件操作詳情_python
- 2023-01-01 Objects?are?not?valid?as?a?React?child報(bào)錯(cuò)解決_React
- 2023-02-01 docker中安裝elasticsarch?等鏡像的過程_docker
- 2022-10-31 ?Go?語言實(shí)現(xiàn)?HTTP?文件上傳和下載_Golang
- 2022-09-16 Linux?Shell如何用ssh命令統(tǒng)計(jì)分布式集群信息詳解_linux shell
- 2022-05-16 .Net?MVC將Controller數(shù)據(jù)傳遞到View_實(shí)用技巧
- 2022-11-22 Python實(shí)例方法與類方法和靜態(tài)方法介紹與區(qū)別分析_python
- 2023-01-26 Python?asyncore?socket客戶端實(shí)現(xiàn)方法詳解_python
- 最近更新
-
- window11 系統(tǒng)安裝 yarn
- 超詳細(xì)win安裝深度學(xué)習(xí)環(huán)境2025年最新版(
- Linux 中運(yùn)行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲(chǔ)小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎(chǔ)操作-- 運(yùn)算符,流程控制 Flo
- 1. Int 和Integer 的區(qū)別,Jav
- spring @retryable不生效的一種
- Spring Security之認(rèn)證信息的處理
- Spring Security之認(rèn)證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權(quán)
- redisson分布式鎖中waittime的設(shè)
- maven:解決release錯(cuò)誤:Artif
- restTemplate使用總結(jié)
- Spring Security之安全異常處理
- MybatisPlus優(yōu)雅實(shí)現(xiàn)加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務(wù)發(fā)現(xiàn)-Nac
- Spring Security之基于HttpR
- Redis 底層數(shù)據(jù)結(jié)構(gòu)-簡(jiǎn)單動(dòng)態(tài)字符串(SD
- arthas操作spring被代理目標(biāo)對(duì)象命令
- Spring中的單例模式應(yīng)用詳解
- 聊聊消息隊(duì)列,發(fā)送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠(yuǎn)程分支