react 采用的是双缓存结构所以应用中是有两颗渲染树的,正在被作用于视图的fiber树这里成为current 树,而另一颗称为workInporgress 树,首次渲染称为Mount时期,此时current 为空, 之后称为Update时期
Fiber 数据结构
function FiberNode(tag, pendingProps, key, mode) {
// Instance
this.tag = tag; //Fiber节点对应实例的类型,
this.key = key;
this.elementType = null; //hook和class 就是对应的函数,和class类,dom 对应的就是就是dom标签名
this.type = null;
this.stateNode = null; // Fiber
this.return = null; //指向 父节点
this.child = null; //指向 孩子节点
this.sibling = null; //指向 兄弟节点
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps; //当前workInprogress Fiber需要更新的props
this.memoizedProps = null; //上一次渲染的props
this.updateQueue = null; //如果是class component ,保存setState 产生的更新链表。如果是hook,存放副作用链表
this.memoizedState = null; //如果是hook 则保存的是所有hook组成的环转链表 如果是class组件保存的是上一次的state
this.dependencies = null; this.mode = mode; // Effects
//副作用标记 eg:Placement --> 插入 ,Update-->更新,Deletion ---> 删除
this.flags = NoFlags;
this.lanes = NoLanes; //优先级相关的
this.alternate = null; //双存储树指向另一个树中对应的fiber节点}
function App() {
const [num, setNum] = useState(1);
return (
<div className="App">
<div className='container'>
<div className='imgBox'>
<img></img>
</div>
<p className='text'>hello</p>
</div>
<button onClick={() => setNum(num + 1)}>{num}</button>
</div>
);}
Fiber中的flags 属性重点说一下,这个属性表示了当前的fiber节点拥有那些副作用,他是一个复数形式说明能不值一个副作用,其中比较常见的比如Placement ---》代表插入操作、Update -->代表将执行更新操作,而很奇怪的是PlacementAndUpdate = Placement | Update
,在源码中对副作用fiber.flags 的操作中大量运用到了按位或 运算,可以理解为,一个fiber 可能拥有多个副作用,通过按位或 方法将多个 副作用合并,例如PlacementAndUpdate代表既有插入操作也有更新操作 而源码中最终的表现形式就是 PlacementAndUpdate = Placement | Update
var NoFlags =/* */0;
var PerformedWork =/* */1; // You can change the rest (and add more).
var Placement =/* */2;
var Update =/* */4;
var PlacementAndUpdate =/* */Placement | Update;var ChildDeletion =/* */16;
var ContentReset =/* */32;
var Callback =/* */64;
var DidCapture =/* */128;
var ForceClientRender =/* */256;
var Ref =/* */512;
var Snapshot =/* */1024;
var Passive =/* */2048;
var Hydrating =/* */4096;
var HydratingAndUpdate =/* */Hydrating | Update;
var Visibility =/* */8192;
var StoreConsistency =/* */16384;
var LifecycleEffectMask = Passive | Update | Callback | Ref | Snapshot | StoreConsistency; // Union of all commit flags (flags with the lifetime of a particular commit)var HostEffectMask =/* */32767; // These are not really side effects, but we still reuse this field.
var Incomplete =/* */32768;
/.
.
.
./more
React 的diff过程
react 的diff过程实际上是通过 深度优先搜索 算法 通过递归对fiber节点遍历的过程,整个递归过程又可以分为 递 阶段和 归阶段 。由于对于整个应用生命周期而言,首次渲染是只存在一个current 渲染树 称为Mount 阶段,之后称为Update 阶段,此时存在 workInprogress 和 current 两颗树,后期整个更新的过程 实际上是 通过diff 时基于 current fiber 生成 对应的workInprogress fiber ,两者通过 alternate 属性 形成映射。最后 FiberRootNode 根结点指向新生成 workinprogress 渲染树,此时workInprogress 渲染树 就成为了 current 渲染树。
前文提到过递归可分为递阶段 和 归 阶段,递阶段 开始于 begainWork() ,归阶段 开始于 completeWork(),接下来看看 递阶段 和 归阶段 具体都干了什么事。
前面说过 react采用的是 深度优先搜索 实现遍历渲染树,当某一个fiber节点没有孩子节点的时候它就会进入归阶段,入口就在 completeWork
function App() {
const [num, setNum] = useState(1);
return (
<div className="App">
<div className='container'>
<div className='imgBox'>
<img></img>
</div>
<p className='text'>hello</p>
</div>
<button onClick={() => setNum(num + 1)}>{num}</button>
</div> );}
以上面的结构为例整个的递归顺序如下
-
div.App --->进入 beginWork
-
div.container ---> 进入beginWork
-
div.imgBox ---> 进入 beginWork
-
img. --->进入begin work
-
img --->进入 completeWork (此时img下没有孩子节点了)
-
div.imgBox ---> 进入 completeWork (因为 img 没有兄弟节点所以直接进入 img 父节点的completeWork)
-
p.text. ---> beginWork(div.imgBox completeWork 结束后发现有sibling 兄弟节点,所以执行兄弟节点的 beginWork)
-
p.text ---> completeWork( 这里 可能会有疑问 ,为啥 p.text 下的文本标签不执行 beginWork ,直接进入p 标签 的completeWork 了,原因在于之前曾说过,react 对单文本节点 做了优化,是不会产生新的fiber节点的,具体可在 reconcileSingleTextNode () 函数中查看)
-
div.container ----> 进入completeWork
-
button ---> beginWork (button 是 div.container 的兄弟节点)
-
button ---> completeWork (child 是 singleTextNode 单文本节点)
-
div.App ---> completeWork
beginWork 递阶段
fiber的tag属性 表示Fiber节点对应实例的类型,在beginWork
函数中会通过tag 的类型进入不同的update逻辑 比如 函数组件 tag = 0 对应FunctionComponent
,类组件tag = 1对应 ClassComponent
,而 普通的dom 元素的tag = 5 对应 HostComponent
,在首次渲染的时候也就是Mount时,函数组件和类组件tag会被赋值为2对应 IndeterminateComponent
意思为不确
定的组件 ,它会调用mountIndeterminateComponent函数对其进行判断然后给予相应的类型。
以下为 所有tag 对映的类型
var FunctionComponent = 0;var ClassComponent = 1;var IndeterminateComponent = 2; // Before we know whether it is function or classvar HostRoot = 3; // Root of a host tree. Could be nested inside another node.var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.var HostComponent = 5;var HostText = 6;var Fragment = 7;var Mode = 8;var ContextConsumer = 9;var ContextProvider = 10;var ForwardRef = 11;var Profiler = 12;var SuspenseComponent = 13;var MemoComponent = 14;var SimpleMemoComponent = 15;var LazyComponent = 16;var IncompleteClassComponent = 17;var DehydratedFragment = 18;var SuspenseListComponent = 19;var ScopeComponent = 21;var OffscreenComponent = 22;var LegacyHiddenComponent = 23;var CacheComponent = 24;
function beginWork(current, workInProgress, renderLanes) {
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
//todo....
} else {
didReceiveUpdate = false;
if (getIsHydrating() && isForkedChild(workInProgress)) {
var slotIndex = workInProgress.index;
var numberOfForks = getForksAtLevel();
pushTreeId(workInProgress, numberOfForks, slotIndex);
}
}
//初始化优先级
workInProgress.lanes = NoLanes;
//通过不同的tag 进行不同的更新
switch (workInProgress.tag) {
case IndeterminateComponent: //初始化 整个应用首次渲染是Function Component和Class Component 走的逻辑
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case FunctionComponent: //函数组件
{
var Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
}
case ClassComponent:// 类组件
{
//.......
return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes);
}
case HostRoot: //FiberRootNode 所走的更新逻辑
return updateHostRoot(current, workInProgress, renderLanes);
case HostComponent: //普通dom元素走的逻辑
return updateHostComponent$1(current, workInProgress, renderLanes);
case HostText:
return updateHostText$1(current, workInProgress);
//......more
}
}
在源码中如何区分Mount时期还是Update时期。beginWork(current, workInProgress, renderLanes)在于current 等不等于null,如果为null
则为mount时期,不为null 则是update时期,但有个例外,在整个应用初始化的时候current和workInProgress都不为null,这个current 代表的是整个应用的根结点 FiberRootNode
后面我就用FiberRootNode 表示根节点,一般为应用挂在的dom元素 <div id="root"></div>
而FiberRootNode 对应的tag 为HostRoot
在beginWork对应的更新逻辑中,都会调用 reconcileChildred(current,workInProgress,nextChildren,renderLanes)方法,这里面看着是调用了两个方法 实际上 mountChildFibers()和 reconcileChildFibers() 调用的是同一个方法ChildReconciler()方法,区别在于 传的参数的不同, 参数为true时 代表需要跟踪副作用标记 false 不需要。
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
//mount 和 update 实际调用同一个函数
// var reconcileChildFibers = ChildReconciler(true);
// var mountChildFibers = ChildReconciler(false);
//差别在于 参数为true是 代表需要跟踪副作用标记 false 不需要
if (current === null) {
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}}
ChildReconciler() 方法实际上是返回一个 基于闭包 返回 reconcileChildFibers()方法,所以实际上调用的是 reconcileChildFibers 方法,其中通过 fiber.$$typeof 属性去判断 走具体的reconcile 逻辑,如果newChild为单节点 执行reconcileSingleElement(),newChild为数组即workInprogressFiber 有多个孩子节点,则执行reconcileChildrenArray()
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, lanes) {
//判断是不是Fragment标签
var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;
//如果是Fragment 标签则忽略他,更行newChild为Fragment 的children
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
} // Handle object types
//$$typeof 走不通的协调逻辑 单节点 执行reconcileSingleElement ,多节点 执行reconcileChildrenArray
//如果是单个的 文本节点的话 走reconcileSingleTextNode ,这里对单文本节点做了优化,如果为单文本节点不会生成Fiber
if (typeof newChild === 'object' && newChild !== null) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE: //单节点
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, lanes));
case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, lanes));
case REACT_LAZY_TYPE:
{
var payload = newChild._payload;
var init = newChild._init; // TODO: This function is supposed to be non-recursive.
return reconcileChildFibers(returnFiber, currentFirstChild, init(payload), lanes);
}
}
if (isArray(newChild)) {
//多节点 最终形成的一条通过sibling指针连接接的一条链表返回第一个头节点
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, lanes);
}
if (typeof newChild === 'string' && newChild !== '' || typeof newChild === 'number') {
//单文本节点 //不会生产Fiber节点
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, lanes));
}
{
if (typeof newChild === 'function') {
warnOnFunctionType(returnFiber);
}
} // Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
以 reconcileSingleElement 为例,真正的diff 比较逻辑 就在这,先比较key , 如果key相同在比较类型 ,如果key 不同则比较 类型,其实还有其他的一些逻辑比较 比如 位置的变化等等
function reconcileSingleElement(returnFiber, currentFirstChild, element, lanes) {
var key = element.key; var child = currentFirstChild;
//diff逻辑
while (child !== null) {
//比较Key
if (child.key === key) {
var elementType = element.type;
if (elementType === REACT_FRAGMENT_TYPE) {
if (child.tag === Fragment) {
deleteRemainingChildren(returnFiber, child.sibling);
var existing = useFiber(child, element.props.children);
existing.return = returnFiber;
{
existing._debugSource = element._source;
existing._debugOwner = element._owner;
}
return existing;
}
} else {
if (child.elementType === elementType || ( // Keep this check inline so it only runs on the false path:
isCompatibleFamilyForHotReloading(child, element) ) || // Lazy types should reconcile their resolved type.
typeof elementType === 'object' && elementType !== null && elementType.$$typeof === REACT_LAZY_TYPE && resolveLazy(elementType) === child.type) {
deleteRemainingChildren(returnFiber, child.sibling);
var _existing = useFiber(child, element.props);
_existing.ref = coerceRef(returnFiber, child, element);
_existing.return = returnFiber;
{
_existing._debugSource = element._source;
_existing._debugOwner = element._owner;
}
return _existing;
}
} // Didn't match.
deleteRemainingChildren(returnFiber, child);
break;
} else {
deleteChild(returnFiber, child);
}
child = child.sibling;
} //创建fiber 并返回
if (element.type === REACT_FRAGMENT_TYPE) {
var created = createFiberFromFragment(element.props.children, returnFiber.mode, lanes, element.key);
created.return = returnFiber;
return created;
} else {
//调用 createFiberFromElement -> createFiberFromTypeAndProps -> var _created4 = createFiberFromElement(element, returnFiber.mode, lanes);
_created4.ref = coerceRef(returnFiber, currentFirstChild, element);
_created4.return = returnFiber;
return _created4;
}
}
最后 创建fiber 并返回。
总结:beginWork 其实就是 根据tag 类型 进入不同的更新逻辑,在不同的update中 其实有共同点就是,先调用reconcileChildren ,然后返回 workInprogress fiber 的第一个孩子,重点是第一个孩子,而reconcileChildren Mount 和 Update 时期都是调用ChildReconciler(shouldTrackSideEffects:boolean),ChildReconciler利用闭包 返回一个名为reconcileChildFibers的函数,这里面通过 判断 newChild.$$typeof 或者 isArray(newChild)或者是否为单文本节点来执行 对应的reconcile 逻辑,所以真正的 比较过程 是发生在这里的。
所以递阶段 beginWork 就是返回 当前workInprogress fiber 的第一个 孩子节点。
completeWork 归阶段
completeWork 具体干了啥,对于 普通的 html dom 元素 ,fiber.tag ==5 == HostComponent
function completeWork(current, workInProgress, renderLanes) {
var newProps = workInProgress.pendingProps; // Note: This intentionally doesn't check if we're hydrating because comparing popTreeContext(workInProgress); switch (workInProgress.tag) { //... case HostComponent: { popHostContext(workInProgress); var rootContainerInstance = getRootHostContainer();//获取跟节点实例 div.root var type = workInProgress.type; //html 标签名 //Update 时期 if (current !== null && workInProgress.stateNode != null) {
updateHostComponent(current, workInProgress, type, newProps, rootContainerInstance);
if (current.ref !== workInProgress.ref) {
markRef(workInProgress);
}
} else {
//Mount时期
var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {//ssr 的不用管
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
markUpdate(workInProgress);
}
} else {
//实际上关键代码
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance; // Certain renderers require commit-time effects for initial mount.
}
}
bubbleProperties(workInProgress);
return null;
}
throw new Error("Unknown unit of work tag (" + workInProgress.tag + "). This error is likely caused by a bug in " + 'React. Please file an issue.');
}
对于HostComponent 普通的html 标签而言,在completeWork 中如果为Mount时期主要就是调用createInstance() 方法
function createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {
var parentNamespace;
//......
var domElement = createElement(type, props, rootContainerInstance, parentNamespace);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props); return domElement;
}
在 createInstance () 方法中主要就是 调用createElement()方法创建domElement并返回 ,实际在内部是通过docuemtn.createElement()方法去创建 dom元素
function createElement(type, props, rootContainerElement, parentNamespace) {
var isCustomComponentTag; // We create tags in the namespace of their parent container, except HTML //拿到当前的 document 对象 var ownerDocument = getOwnerDocumentFromRootContainer(rootContainerElement); var domElement; var namespaceURI = parentNamespace;
if (namespaceURI === HTML_NAMESPACE) {
//创建script 标签 if (type === 'script') {
var div = ownerDocument.createElement('div');
div.innerHTML = '<script><' + '/script>';
// eslint-disable-line
var firstChild = div.firstChild;
//删除外层的div标签 保存 script 标签
domElement = div.removeChild(firstChild);
} else if (typeof props.is === 'string') {
// $FlowIssue `createElement` should be updated for Web Components
domElement = ownerDocument.createElement(type, {
is: props.is
});
} else { //根据标签名 创建 dom元素 在web中实际上就是 document.createElement()
domElement = ownerDocument.createElement(type); // Normally attributes are assigned in `setInitialDOMProperties`, however the `multiple` and `size`
if (type === 'select') {
var node = domElement;
if (props.multiple) {
node.multiple = true;
} else if (props.size) {
node.size = props.size;
}
}
}
} else {
domElement = ownerDocument.createElementNS(namespaceURI, type);
}
{
if (namespaceURI === HTML_NAMESPACE) {
if (!isCustomComponentTag && Object.prototype.toString.call(domElement) === '[object HTMLUnknownElement]' && !hasOwnProperty.call(warnedUnknownTags, type)) {
warnedUnknownTags[type] = true;
error('The tag <%s> is unrecognized in this browser. ' + 'If you meant to render a React component, start its name with ' + 'an uppercase letter.', type);
}
}
}
return domElement;
}
总结:completeWork 对于 HostComponent Mount时期根据fiber 信息 ,调用document.createElement方法创建dom的过程,Update 主要调用UpdateHostComponent 方法进行更新。
Hook
对于函数组件而言,整个的调用顺序为 beginWork----> 判断tag--->调用 updateFunctionComponent --->renderWithHooks,所以组件真正的调用发生在renderWithHooks 中,
function renderWithHooks(current, workInProgress, Component, props, secondArg, nextRenderLanes) {
renderLanes = nextRenderLanes; //记录当前正在执行的Fiber节点
currentlyRenderingFiber$1 = workInProgress;
//初始化
workInProgress.memoizedState = null;
workInProgress.updateQueue = null;
workInProgress.lanes = NoLanes; // The following should have already been reset
{ //判断dispatcher 的指向,是执行mount的方法还是update的方法
if (current !== null && current.memoizedState !== null) {
ReactCurrentDispatcher$1.current = HooksDispatcherOnUpdateInDEV;
} else if (hookTypesDev !== null) {
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountWithHookTypesInDEV;
} else {
ReactCurrentDispatcher$1.current = HooksDispatcherOnMountInDEV;
}
} //render 执行构造函数,Component实际上就是 我们的函数组件方法
var children = Component(props, secondArg); // Check if there was a render phase update
//didScheduleRenderPhaseUpdateDuringThisPass 全局变量 判断是否需要 从新render。因为在执行构造函数的时候 只要使用了setstate就会产生 re-render
if (didScheduleRenderPhaseUpdateDuringThisPass) {
//进行 re-render 逻辑
var numberOfReRenders = 0;
do {
didScheduleRenderPhaseUpdateDuringThisPass = false;
localIdCounter = 0;
numberOfReRenders += 1;//记录reRender次数
currentHook = null;
workInProgressHook = null;
workInProgress.updateQueue = null;
ReactCurrentDispatcher$1.current = HooksDispatcherOnRerenderInDEV ;
children = Component(props, secondArg);
} while (didScheduleRenderPhaseUpdateDuringThisPass);
} //执行完后关闭hooks的调用接口
ReactCurrentDispatcher$1.current = ContextOnlyDispatcher;
//....
return children;
}
例如这个组件
function Apple() {
const [num, setNum] = useState(1);
const [name, setName] = useState("");
const [age, setAge] = useState(23);
const handleAdd = () => {
setNum("德玛西亚");
setNum("洛克萨斯");
setAge(age + 1);
};
//这段代码就会引起re-render ,他在组件render的过程中调用了set方法
if (num == 1) {
setNum(num + 1);
}
useEffect(() => {
setName("yangxiang-杨祥");
}, []);
return (
<div className="apple">
<p>
Apple {num},age{age}
</p>
<button onClick={() => handleAdd()}>add Apple</button>
</div>
)
;}
上述renderWithHooks 中的 Component 调用就是 Apple 方法的调用,所以的 hooks调用 也都发生在这Apple函数里,至于之前在renderWithHook中讲到的 didScheduleRenderPhaseUpdateDuringThisPass。产生re-render 的情况,就如上面Apple 组建里面的 if(num==1){setNum(num+1)} 函数组件执行的同时调用 set 方法出发更新 就会产生 re-render , 不用 整棵树从新渲染。
首先在学习hook源码之前先了解一下hook这一块相关的数据结构。
var hook = {
memoizedState: null,
baseState: null,
baseQueue: null,
queue: null,
next: null
};
对于不同的hook 里面所存放的东西各不相同,比如useState 产生的hook,它的memoizedState保存的就是就是你返回的state,比如useEffect,保存的就是一条effect 链表,后面具体细说。
useState/useEffect产生的hook
var hook = {
memoizedState: null, //保存最新的状态
baseState: null, //保存状态
baseQueue: null, //更新中断未更新的update 链表
queue: null, //queue.pending 指向update环状链表的最后一个update
next: null //下一个hook
};
useEffect/useLayoutEffect产生的hook
var hook = {
memoizedState: null, //保存effect 链表
baseState: null,
baseQueue: null,
queue: null,
next: null //下一个hook
};
useRef
var hook = {
memoizedState: null, //{current:XXX}的对象
baseState: null,
baseQueue: null,
queue: null,
next: null //下一个hook
};
useCallback
var hook = {
memoizedState: null, //保存一个数组 [callback,deps],第零位 未 传入的方法,第一位为依赖项
baseState: null,
baseQueue: null,
queue: null,
next: null //下一个hook
};
useMemo
var hook = {
memoizedState: null, //保存一个数组[nextvalue,nextDeps], 一个是值,一个是依赖项
baseState: null,
baseQueue: null,
queue: null,
next: null //下一个hook
};
useState
之前在renderWithHook()方法中曾经提到过,ReactCurrentDispatcher$1.current
所指向的就是我们要调用的hook方法,每一个官方提供的hook 方法在mount 和 update 的不同时期都会调用不同的方法,比如useState 对映有mountState,updateState两个方法,首先这里说明一下这里的mount和update是相对于hook方法的生命周期而言,而之前在讲react的mount和update是指的是应用的生命周期而言。
首先来介绍一下useState,中涉及到的数据结构update,每次调用useState 返回的dispatch函数,也就是 返回数组的第二个,都会生成一个update
var update = {
lane: lane, //优先级
action: action, //可以是一个值,可以是一个函数,比如下方第一个setNum产生的update中action = 1,
//第二个action = ()=> 'yangxiang '
hasEagerState: false,
eagerState: null,
next: null //指向下一个update的指针
};
cosnt [num,setNum] = useState()
cosnt [name,setName] = useState()
我们首先来理一下 hook 数据结构,update 数据节后 ,和fiber 之前的关系
fiber对映了一个工作单元,比如我们的组件,原生dom元素等等,hook 是我们在function Component 中执行官方提供的hook方法,比如执行一个useState()就会产生一个对应的hook,而useState提供了一个我们用于改变状态的set方法比如上面的setNum、setName,每执行一次set方法就会产生一个update.
下面为几个数据结构的关系
fiber----包含---> hook -----包含--->update
mountState
function mountState(initialState) {
//初始化hook
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
//保存状态
hook.memoizedState = hook.baseState = initialState;
var queue = {
pending: null,//一个指向由update组成的环转链表最后一个update的指针
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
};
hook.queue = queue;
var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
}
- 初始化hook
- 处理initialState 为函数的情况
- 保存初始化状态到hook.memoizedState和hook.baseState中
- 生成一个queue ,queue中最重要的作用之一就是通过queue.pending 指向update环转链表的最后一个update
- 生成每一个useState对应的dispatch函数,也就是我们用来改变状态的那个set方法
- 最后返回当前的状态和dispatch方法
我们来看看mountWorkInProgressHook都做了什么
function mountWorkInProgressHook() {
var hook = {
memoizedState: null,
baseState: null,
baseQueue: null, //
queue: null,
next: null
};
if (workInProgressHook === null) {
// This is the first hook in the list
currentlyRenderingFiber$1.memoizedState = workInProgressHook = hook;
} else {
// Append to the end of the list
workInProgressHook = workInProgressHook.next = hook;
}
return workInProgressHook;
}
其实很简单,
- 创建一个hook 数据结构,
- workInProgressHook 即为当前正在执行中的hook,如果 workInProgressHook === null ,则吧创建的hook 赋值给workInProgressHook,并且挂载到当前正在渲染的fiber.memoizedState上,否则就放到链表的最后一个
- 最后返回创建的hook
而另一个需要注意的就是产生用于更新的dispatch函数的方法:dispatchSetState
function dispatchSetState(fiber, queue, action) {
{
if (typeof arguments[3] === 'function') {
error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().');
}
}
var lane = requestUpdateLane(fiber);
var update = {
lane: lane,
action: action,
hasEagerState: false,
eagerState: null,
next: null
};
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
enqueueUpdate$1(fiber, queue, update);
var alternate = fiber.alternate;
if (fiber.lanes === NoLanes && (alternate === null || alternate.lanes === NoLanes)) {
var lastRenderedReducer = queue.lastRenderedReducer;
if (lastRenderedReducer !== null) {
var prevDispatcher;
{
prevDispatcher = ReactCurrentDispatcher$1.current;
ReactCurrentDispatcher$1.current = InvalidNestedHooksDispatcherOnUpdateInDEV;
}
try {
var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute
update.hasEagerState = true;
update.eagerState = eagerState;
if (objectIs(eagerState, currentState)) {
return;
}
} catch (error) {// Suppress the error. It will throw again in the render phase.
} finally {
{
ReactCurrentDispatcher$1.current = prevDispatcher;
}
}
}
}
var eventTime = requestEventTime();
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane);
}
上述为完整代码实际上的核心代码在这里
function dispatchSetState(fiber, queue, action) {
var update = {
lane: lane,
action: action,
hasEagerState: false,
eagerState: null,
next: null
};
//是否在渲染阶段更新
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
enqueueUpdate$1(fiber, queue, update);
var alternate = fiber.alternate;
var eventTime = requestEventTime();
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}
markUpdateInDevTools(fiber, lane);
}
- 每次调用修改状态的set方法生成一个update数据结构
- 判断是否在渲染阶段更新
- 调用scheduleUpdateOnFiber ,执行调度方法,实现更新
其中主要的代码在enqueueRenderPhaseUpdate()和enqueueUpdate()中,具体来看看做了啥
enqueueRenderPhaseUpdate
function enqueueRenderPhaseUpdate(queue, update) {
didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true;
var pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
- 进入这个方法说明是在渲染阶段进行了更新, didScheduleRenderPhaseUpdateDuringThisPass = didScheduleRenderPhaseUpdate = true ,触发 renderWithHook 方法里面曾讲到的re-render逻辑
- 判断 hook.queue.pending 是否为空,如果为空则初始化换装链表,不为空则将生成的update放到链表的最后一个
- 把hook.queue.pending 指向最后一个update,对应了之前讲的
enqueueUpdate
function enqueueUpdate$1(fiber, queue, update, lane) {
//是否是多根节点交错更新的情况,非常少见
if (isInterleavedUpdate(fiber)) {
var interleaved = queue.interleaved;
if (interleaved === null) {
// This is the first update. Create a circular list.
update.next = update; // At the end of the current render, this queue's interleaved updates will
// be transferred to the pending queue.
pushInterleavedQueue(queue);
} else {
update.next = interleaved.next;
interleaved.next = update;
}
queue.interleaved = update;
} else {
var pending = queue.pending;
if (pending === null) {
// This is the first update. Create a circular list.
//第一个update,生成环状链表
update.next = update;
} else {
update.next = pending.next;
pending.next = update;
}
queue.pending = update;
}
}
- 以判断是否是多根节点交错更新的情况,非常少见(没实际调试过)
- 核心代码同样是判断pending是否为空,如果等于null则 生成 update的环转链表,否则插入到链表的末尾,最后通过queue.pending 指向最后一个
到此为止mountState部分学习到此为止了
updateState
在hook的update时期调用的就是updateState方法,也就是我们调用对应改变state的dispatch函数的时候,比如 [num,setNum] = useState(1) ,执行setNum(2) 方法时就会执行当前hook方法的update方法
function updateState(initialState) {
return updateReducer(basicStateReducer)
;
}
以上就是updateState全部内容,其实update时期调用的竟然是updateReducer,有点意思,接下来看看updateReducer 究竟做了啥,同时这里的basicStateReducer 又是什么。
function updateReducer(reducer, initialArg, init) {
var hook = updateWorkInProgressHook();
var queue = hook.queue; //前面讲了,hook.queue.pending 指向update环转链表的最后一个update,即链表尾部
if (queue === null) { //mount阶段产生了queue并挂在到了hook上,update阶段 必须有queue否则报错
throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.');
}
queue.lastRenderedReducer = reducer; //这里传的basicStateReducer,后面会讲 var current = currentHook; // The last rebase update that is NOT part of the base state.
//由于之前某些高优先级任务导致更新中断,baseQueue记录的就是尚未处理的最后一个update
var baseQueue = current.baseQueue; // The last pending update that hasn't been processed yet.
var pendingQueue = queue.pending; //当前update链表最后一个update
if (pendingQueue !== null) {
if (baseQueue !== null) { // Merge the pending queue and the base queue.
//合并上述的baseQueue和pendingQueue
var baseFirst = baseQueue.next; //未处理的update环转链表的第一个update
var pendingFirst = pendingQueue.next; //当前产生的update链表的第一个update
baseQueue.next = pendingFirst; //当前产生的更新接入,之前未处理的链表后,baseQueue的尾巴接入pendingQueue的头
pendingQueue.next = baseFirst; //pendingQueue的尾部接入baseQueue的头,最后形成 环
}
{
if (current.baseQueue !== baseQueue) { // Internal invariant that should never happen, but feasibly could in
// the future if we implement resuming, or some form of that.
error('Internal error: Expected work-in-progress queue to be a clone. ' + 'This is a bug in React.');
}
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) { // We have a queue to process. 产生的update链表不为空
var first = baseQueue.next; //拿到第一个update
var newState = current.baseState; //useState hook当前的state
var newBaseState = null;
var newBaseQueueFirst = null;
var newBaseQueueLast = null;
var update = first; //刚开始 update 变量 赋值为 链表中第一个更新
//这里的do. while 循环则是在遍历刚刚我们合并的update链表,如果baseQueue === null, 不存在
// 之前的合并操作,整个do{}里面包含的就是 产生心的state的过程
do {
var updateLane = update.lane; //lane优先级
if (!isSubsetOfLanes(renderLanes, updateLane)) {
var clone = {
lane: updateLane,
action: update.action,
hasEagerState: update.hasEagerState,
eagerState: update.eagerState,
next: null
};
if (newBaseQueueLast === null) {
newBaseQueueFirst = newBaseQueueLast = clone; newBaseState = newState; } else { newBaseQueueLast = newBaseQueueLast.next = clone; } currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, updateLane); markSkippedUpdateLanes(updateLane); } else { if (newBaseQueueLast !== null) { var _clone = { lane: NoLane, action: update.action, hasEagerState: update.hasEagerState, eagerState: update.eagerState, next: null }; newBaseQueueLast = newBaseQueueLast.next = _clone; } // Process this update. if (update.hasEagerState) { // If this update is a state update (not a reducer) and was processed eagerly, // we can use the eagerly computed state newState = update.eagerState; } else {
//核心代码就在这,
var action = update.action; //取得当前的update的action,可能是函数也可能是具体的值
newState = reducer(newState, action);
}
}
update = update.next; //更新update指针指向下一个更新
} while (update !== null && update !== first); //条件就是把整条环转链表更新完
if (newBaseQueueLast === null) {
newBaseState = newState;
} else {
newBaseQueueLast.next = newBaseQueueFirst;
} // Mark that the fiber performed work, but only if the new state is // different from the current state.
if (!objectIs(newState, hook.memoizedState)) { markWorkInProgressReceivedUpdate();
}
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
var lastInterleaved = queue.interleaved;
if (lastInterleaved !== null) {
var interleaved = lastInterleaved;
do {
var interleavedLane = interleaved.lane;
currentlyRenderingFiber$1.lanes = mergeLanes(currentlyRenderingFiber$1.lanes, interleavedLane);
markSkippedUpdateLanes(interleavedLane);
interleaved = interleaved.next;
} while (interleaved !== lastInterleaved);
} else if (baseQueue === null) {
// `queue.lanes` is used for entangling transitions. We can set it back to // zero once the queue is empty.
queue.lanes = NoLanes;
}
var dispatch = queue.dispatch;
return [hook.memoizedState, dispatch];
}
以上为完整代码,看起来有点太多了,精简一下核心常用的代码
function updateReducer(reducer, initialArg, init) {
var hook = updateWorkInProgressHook();//更新当前执行的hook
var queue = hook.queue; //前面讲了,hook.queue.pending 指向update环转链表的最后一个update,即链表尾部
if (queue === null) { //mount阶段产生了queue并挂在到了hook上,update阶段 必须有queue否则报错
throw new Error('Should have a queue. This is likely a bug in React. Please file an issue.');
}
queue.lastRenderedReducer = reducer; //这里传的basicStateReducer,后面会讲
var current = currentHook; // The last rebase update that is NOT part of the base state.
//由于之前某些高优先级任务导致更新中断,baseQueue记录的就是尚未处理的最后一个update
var baseQueue = current.baseQueue; // The last pending update that hasn't been processed yet.
var pendingQueue = queue.pending; //当前update链表最后一个update if (pendingQueue !== null) {
if (baseQueue !== null) {
// Merge the pending queue and the base queue.
//合并上述的baseQueue和pendingQueue
var baseFirst = baseQueue.next;
//未处理的update环转链表的第一个update
var pendingFirst = pendingQueue.next; //当前产生的update链表的第一个update
baseQueue.next = pendingFirst;
//当前产生的更新接入,之前未处理的链表后,baseQueue的尾巴接入pendingQueue的头
pendingQueue.next = baseFirst; //pendingQueue的尾部接入baseQueue的头,最后形成 环
}
current.baseQueue = baseQueue = pendingQueue;
queue.pending = null;
}
if (baseQueue !== null) {
// We have a queue to process. 产生的update链表不为空
var first = baseQueue.next; //拿到第一个update
var newState = current.baseState; //useState hook当前的state
var newBaseState = null;
var newBaseQueueFirst = null;
var newBaseQueueLast = null;
var update = first; //刚开始 update 变量 赋值为 链表中第一个更新
//这里的do. while 循环则是在遍历刚刚我们合并的update链表,如果baseQueue === null, 不存在
// 之前的合并操作,整个do{}里面包含的就是 产生心的state的过程
do {
var updateLane = update.lane; //lane优先级
//...more...
if (update.hasEagerState) {
// If this update is a state update (not a reducer) and was processed eagerly,
// we can use the eagerly computed state
newState = update.eagerState;
} else {
//核心代码就在这,
var action = update.action; //取得当前的update的action,可能是函数也可能是具体的值
newState = reducer(newState, action);
}
}
update = update.next; //更新update指针指向下一个更新
} while (update !== null && update !== first); //条件就是把整条环转链表更新完
//把最终得倒的状态更新到 hook上
hook.memoizedState = newState;
hook.baseState = newBaseState;
hook.baseQueue = newBaseQueueLast;
queue.lastRenderedState = newState;
}
var dispatch = queue.dispatch;
//返回state和dispatch
return [hook.memoizedState, dispatch];
}
从新梳理一下
-
通过updateWorkInProgressHook 方法更新 当前执行的hook
-
把baseQueue和pendingQueue合并,(baseQueue,为之前因为某些原因导致更新中断从而剩下的update链表,pendingQueue则是本次产生的update链表,将他们首位合并形成新的链表,注意在 do. while 循环之前的第一个firstUpdate 变量赋值的是baseQueue.next,说明了整个合并的链表是先执行上一次更新剩下的在执行新的,这样我们的调用拿到的才是正确的数据,举个例子, 这个例子中每次调用setName,我都能拿到上一次的oldState
-
通过循环遍历每次产生的newState,作为下一次的参数,直到遍历完整个链表
-
最后更新hook上的参数,返回state和dispatch
const [name,setName] = useState('yangxiang')
setName((oldState)=>{ return oldState + 'hhaha ' //此时oldState == 'yangxiang' })
setName((oldState)=>{ return oldState + '奥德曼' //此时oldState == 'yangxianghhaha '})
上述代码中的reducer参数之前说过是basicStateReducer()方法,具体看看他都做了啥
function basicStateReducer(state, action) {
return typeof action === 'function' ? action(state) : action;
}
完整代码就这..., 好吧其实就是判断了一下action是不是函数如果是,则执行函数并把state作为参数,如果不是则直接返回action。
useState简单实现
let isMount = true;let workInProgressHook = null;const fiber = { memoizedState: null, stateNode: App};function schedule() { isMount = false; workInProgressHook = fiber.memoizedState; fiber.stateNode();}function useState(initailState) { let hook; if (isMount) { hook = { queue: { pending: null }, memoizedState: typeof initailState == "function" ? initailState() : initailState, next: null }; if (!fiber.memoizedState) { fiber.memoizedState = hook; } else { workInProgressHook.next = hook; } workInProgressHook = hook; } else { hook = workInProgressHook; workInProgressHook = workInProgressHook.next; } let baseState = hook.memoizedState; // console.log(hook.queue.pending) if (hook.queue.pending) { let currentUpdate = hook.queue.pending.next; do { let action = currentUpdate.action; baseState = action(baseState); // console.log('baseState',baseState,typeof baseState) currentUpdate = currentUpdate.next; } while (currentUpdate != null && currentUpdate != hook.queue.pending.next); hook.queue.pending = null; } hook.memoizedState = baseState; return [baseState, dispatchSetState.bind(null, hook.queue)];}function dispatchSetState(queue, action) { // console.log('action-->',action) let update = { action: action, next: null }; let pending = queue.pending; if (pending == null) { update.next = update; } else { update.next = pending.next; pending.next = update; } queue.pending = update; schedule();}function App() { const [name, setName] = useState(1); console.log("name", name); appendDom(`currentValue----> ${name}`); return { onclick: () => { setName((oldVale) => { console.log("oldValue", oldVale); appendDom(`oldvalue ---> ` + oldVale); return oldVale + 1; }); } };}let obj = App();obj.onclick();obj.onclick();obj.onclick();obj.onclick();
demo 地址
useReducer
useState
的替代方案。它接收一个形如(state, action) => newState
的 reducer,并返回当前的 state 以及与其配套的dispatch
方法。(如果你熟悉 Redux 的话,就已经知道它如何工作了。)
在某些场景下,useReducer
会比useState
更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等。并且,使用useReducer
还能给那些会触发深更新的组件做性能优化,因为你可以向子组件传递dispatch
而不是回调函数 。
const initialState = {count: 0};
function init(initialCount) { return {count: initialCount};}
function reducer(state, action) {
switch (action.type) {
case 'increment':
return {count: state.count + 1};
case 'decrement':
return {count: state.count - 1};
default:
throw new Error();
}
}
function Counter() {
const [state, dispatch] = useReducer(reducer, initialState);
const [state, dispatch] = useReducer(reducer, initialState,init);//惰性初始化
return (
<>
Count: {state.count}
<button onClick={() => dispatch({type: 'decrement'})}>-</button>
<button onClick={() => dispatch({type: 'increment'})}>+</button>
</>
);
}
这是一个基本使用
mountReducer
function mountReducer(reducer, initialArg, init) {
var hook = mountWorkInProgressHook(); //初始化hook,和之前 mouState一样的
var initialState;
//初始化initialState 赋值
//按照上方的例子 initialArg = initialState = {count: 0}
if (init !== undefined) {
initialState = init(initialArg); //init 是做惰性初始化的具体可看官网
} else {
initialState = initialArg;
}
hook.memoizedState = hook.baseState = initialState;
var queue = {
pending: null,
interleaved: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState
};
hook.queue = queue;
var dispatch = queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
}
-
和mountState 一样 初始化一个hook,其实其他的hook方法在mount时第一步也是如此。
-
判断是否进行惰性加载,然后初始化initialState
-
hook.memoizedState = hook.baseState = initialState 同时生成一个queue数据结构
-
调用dispatchReducerAction,返回dispatch
-
最后return return [hook.memoizedState, dispatch];
有上述可知其实和mountState差不多,需要关注的是dispatchReducerAction这个方法。
function dispatchReducerAction(fiber, queue, action) {
{
if (typeof arguments[3] === 'function') {
error("State updates from the useState() and useReducer() Hooks don't support the " + 'second callback argument. To execute a side effect after ' + 'rendering, declare it in the component body with useEffect().');
}
}
var lane = requestUpdateLane(fiber);
var update = {//生成一个update
lane: lane,
action: action,
hasEagerState: false,
eagerState: null,
next: null
};
if (isRenderPhaseUpdate(fiber)) {
enqueueRenderPhaseUpdate(queue, update);
} else {
enqueueUpdate$1(fiber, queue, update);
var eventTime = requestEventTime();
var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
if (root !== null) {
entangleTransitionUpdate(root, queue, lane);
}
}
markUpdateInDevTools(fiber, lane);
}
其实和之前的在mountState 中调用的 dispatchSetState 方法逻辑是差不多的,做的事情也是差不多的,这里就不再重复的讲了
useEffect
之前在讲解useState阶段曾经说过有个update 数据结构,而在这里 也有一个 特殊的数据结构
effect,他和hook,fiber,的关系 和useState的update差不多
var effect = {
tag: tag, //副作用标记
create: create, //传入的方法
destroy: destroy, //return的销毁方法
deps: deps, // 依赖项
next: null //下一个effect
};
mountEffect
话不多说直接看代码
function mountEffect(create, deps) {
if ( (currentlyRenderingFiber$1.mode & StrictEffectsMode) !== NoMode) {
return mountEffectImpl(MountPassiveDev | Passive | PassiveStatic, Passive$1, create, deps);
} else {
return mountEffectImpl(Passive | PassiveStatic, Passive$1, create, deps);
}
}
这就是mouthEffect 全部代码,create 参数就是我们传给useEffect(fn,[])的函数,deps参数就是依赖项。
-
首先对当前fiber.mode 与 StrictEffectsMode 严格模式进行与运算,其中的区别就是副作用的差别一个多了一个
MountPassiveDev
,另一个没有(目前不太理解这一步的具体原因,有知道的大佬麻烦留言告诉我一下) -
之后就是调用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); }
由这个源代码可知
- 首先生成调用mountWorkInProgressHook() 生成一个hook
- 判断依赖项是否为空
- 进行副作用合并 ,并赋值给fiber
- 调用pushEffect(),将useEffect生成的effect 链表挂载到hook.memoizedState上
接下来看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环状链表的最后一个
//这里和之前 useState生成 update 换装链表的过程一样
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
var lastEffect = componentUpdateQueue.lastEffect;
if (lastEffect === null) {
//这里同样是生成effect 环状链表的过程
componentUpdateQueue.lastEffect = effect.next = effect;
} else {
//对生成的effect 插入链表尾部的操作
var firstEffect = lastEffect.next;
lastEffect.next = effect;
effect.next = firstEffect;
componentUpdateQueue.lastEffect = effect;
}
}
return effect;
}
- 生成一个effect 的数据结构
- 判断currentlyRenderingFiber$1.updateQueue 是否为空,至于updateQueue上具体是啥,后面你就知道了
- 其实就是根据不同条件执行 生成effect 环状链表 ,还是把生成的 effect 插入链表尾部的过程,从上面源码可知对于functionComponent ,fiber.updateQueue.lastEffect指针就是指向当前functionComponent的effect链表的最后一个 effect
createFunctionComponentUpdateQueue()方法,从名字就能看出就是创建一个updateQueue,接下来看看具体怎么实现的
function createFunctionComponentUpdateQueue() {
return {
lastEffect: null,
stores: null
};
}
好了非常nice,就这。。。
updateEffect
function updateEffect(create, deps) {
return updateEffectImpl(Passive, Passive$1, create, deps);
}
function updateEffectImpl(fiberFlags, hookFlags, create, deps) {
var hook = updateWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var destroy = undefined;
if (currentHook !== null) {
//在之前介绍mountEffectImpl时生成的 effect 是挂载到 hook.memoizedState 属性上的
//这里拿到之前的 effect ,同时如果有destory方法,也就是我们return的那个方法也拿出来
var prevEffect = currentHook.memoizedState;
destroy = prevEffect.destroy;
if (nextDeps !== null) {
var prevDeps = prevEffect.deps;
if (areHookInputsEqual(nextDeps, prevDeps)) {//通过areHookInputsEqual方法比较依赖项是否改变
hook.memoizedState = pushEffect(hookFlags, create, destroy, nextDeps);
return;
}
}
}
currentlyRenderingFiber$1.flags |= fiberFlags;
hook.memoizedState = pushEffect(HasEffect | hookFlags, create, destroy, nextDeps);
}
在updateEffectImpl中
- 调用updateWorkInProgressHook(),之前讲过,更新当前正在执行的hook,也就是workInprogressHook
- 处理依赖项
- 命中if判断后进入其中主要做了,在之前介绍mountEffectImpl时生成的 effect 是挂载到 hook.memoizedState 属性上的 ,这里拿到之前的 effect ,同时如果有destory方法,也就是我们return的那个方法也拿出来,然后通过 areHookInputsEqual(nextDeps,prevDeps)比较前后依赖项有没有改变
- 同样是调用pushEffect 方法,和之前讲的一样
useRef
function mountRef(initialValue) {
var hook = mountWorkInProgressHook();
{
var _ref2 = {
current: initialValue
};
hook.memoizedState = _ref2;
return _ref2;
}
}
function updateRef(initialValue) {
var hook = updateWorkInProgressHook();
return hook.memoizedState;
}
从上面的代码可以知道,通过useRef 创建的 ref ,仅仅就是一个包含 一个 current 属性的 对象,同时挂在到hook 的memoizedState 属性上,update时直接返回 挂载在 hook.memoizedState上的 对象,这也就解释了为什么ref ,在整个组件的生命周期内不会发生改变,除非我们手动的通过 ref.current = 'xxx' 去修改。
useCallback/useMemo
mountCallback/mountMemo
function mountCallback(callback, deps) {
var hook = mountWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
hook.memoizedState = [callback, nextDeps];
return callback;
}
function mountMemo(nextCreate, deps) {
var hook = mountWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}
代码一看就懂,区别就是一个执行了函数返回值,一个直接返回函数
updateCallback/updateMemo
function updateCallback(callback, deps) {
var hook = updateWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var prevState = hook.memoizedState;
if (prevState !== null) {
if (nextDeps !== null) {
var prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) { //比较前后依赖 如果相同就返回之前保存的
return prevState[0];
}
}
}
//否则就返回 新的 callback
hook.memoizedState = [callback, nextDeps];
return callback;
}
function updateMemo(nextCreate, deps) {
var hook = updateWorkInProgressHook();
var nextDeps = deps === undefined ? null : deps;
var prevState = hook.memoizedState;
if (prevState !== null) {
// Assume these are defined. If they're not, areHookInputsEqual will warn.
if (nextDeps !== null) {
var prevDeps = prevState[1];
if (areHookInputsEqual(nextDeps, prevDeps)) { //比较 前后依赖项 ,相同返回之前的值
return prevState[0];
}
}
}
//否则 返回 新的值,并把新的值和依赖项保存在hook.memoizedState上
var nextValue = nextCreate();
hook.memoizedState = [nextValue, nextDeps];
return nextValue;
}