共计 4451 个字符,预计需要花费 12 分钟才能阅读完成。
注:本文应用的版本是 React-17.0.0.3,开启 enableNewReconciler = true,即会应用 new.js 后缀的文件;
文章专一于 hook 原理和 react 的渲染局部,波及到 classComponent 的局部一律略过;
mode = concurrent
本文所有代码都是基于以下 react 代码
import {React} from "../CONST";
import {useEffect, useState} from "../react/packages/react";
function FunctionComponent({name}) {const [count, setCount] = useState(0);
const [subtraction, setSubTraction] = useState(1);
useEffect(() => {setSubTraction(10);
}, []);
return (
<div className="function border">
{name} {count} {count1}
<button onClick={() => setCount(pre => pre + 1)}>click</button>
</div>
);
}
const jsx = (
<div className="box border">
<p>start debugger</p>
<FunctionComponent name="函数组件" />
</div>
);
ReactDOM.createRoot(document.getElementById('root')
).render(jsx);
自顶向下介绍 React
对于 concurrent 模式和 Fiber 架构等常识请看《React 技术揭秘》
fiber 的构造
function FiberNode(
tag: WorkTag, // fiberTag
pendingProps: mixed, // 组件参数 / 属性
key: null | string, // key
mode: TypeOfMode, // 批示是 lagecy 还是 conCurrent 模式
) {
// Instance
this.tag = tag; // fiber 类型
this.key = key; // 用来做 diff 算法
this.elementType = null;
this.type = null;
this.stateNode = null; // 该 fiber 关联的实在 dom
// Fiber
this.return = null; // 父 fiber
this.child = null; // 第一个孩子 fiber
this.sibling = null; // 相邻的第一个兄弟 fiber
this.index = 0;
this.ref = null;
this.pendingProps = pendingProps; // 新的组件参数 / 属性
this.memoizedProps = null; // 曾经渲染在页面上的(旧的)组件参数 / 属性
this.updateQueue = null; // 一个环状链表,存储的是 effect 副作用造成的 update 对象
this.memoizedState = null; // 对于 functionComponent 来说,存储的是 hook 对象链表
this.dependencies = null;
this.mode = mode;
// Effects
this.flags = NoFlags; // 本 fiber effect 造成的副作用
this.subtreeFlags = NoFlags; // 子 fiber effect 的副作用
this.deletions = null;
this.lanes = NoLanes; // 本 fiber 的 update lanes
this.childLanes = NoLanes; // 所有子 fiber 的 update lanes
this.alternate = null;。。。}
fibertag 用来标示该 fiber 是哪种组件类型的 fiber–25 种
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
....
fiber 树和 dom 的比照
fiber 树的生成过程
咱们从 render 开始看 fiber 树是如何一个一个生成的
export function createRoot(
container: Container,
options?: RootOptions,
): RootType {return new ReactDOMRoot(container, options);
}
function ReactDOMRoot(container: Container, options: void | RootOptions) {this._internalRoot = createRootImpl(container, ConcurrentRoot, options);
}
function createRootImpl(
container: Container,
tag: RootTag,
options: void | RootOptions,
) {
...
// 生成 root
const root = createContainer(
container,
tag,
hydrate,
hydrationCallbacks,
isStrictMode,
concurrentUpdatesByDefaultOverride,
);
...
return root;
}
ReactDOMRoot.prototype.render = ReactDOMLegacyRoot.prototype.render = function(children: ReactNodeList,): void {
const root = this._internalRoot;
...
updateContainer(children, root, null, null);
};
ReactDOM.createRoot(document.getElementById(‘root’)) 会创立 ReactDomRoot 对象,该类会调用 createRootImpl 初始化 fiberRoot 对象。随后的 render 函数是挂在类原型上的,会调用 updateContainer
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
const current = container.current;
const eventTime = requestEventTime();
const lane = requestUpdateLane(current);
const update = createUpdate(eventTime, lane);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {update.callback = callback;}
enqueueUpdate(current, update, lane);
const root = scheduleUpdateOnFiber(current, lane, eventTime);
return lane;
}
显然该函数是为了给 fiberRoot 建设一个 update,并且 update.payload 是 element 即 jsx;enqueueUpdate 是为了把 update 挂到 fiber.updateQueue;最初调用了 scheduleUpdateOnFiber
重点来了,scheduleUpdateOnFiber 是调度函数的入口函数,从这里开始进行 fiber 树的结构以及 update 的解决;
export function scheduleUpdateOnFiber(
fiber: Fiber,
lane: Lane,
eventTime: number,
): FiberRoot | null {
// 从以后 fiber 开始向上冒泡直到找到 root 节点,同时更新所有父节点的 childLanes
const root = markUpdateLaneFromFiberToRoot(fiber, lane);
if (root === null) {return null;}
// 标记 root 有一个待更新
markRootUpdated(root, lane, eventTime);
if (lane === SyncLane) {
if (
// Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext &&
// Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext
) {performSyncWorkOnRoot(root);
} else {ensureRootIsScheduled(root, eventTime);
}
} else {
// Schedule other updates after in case the callback is sync.
ensureRootIsScheduled(root, eventTime);
}
return root;
}
ensureRootIsScheduled 函数是为了向调度堆里 push 一个回调函数,最初还是会调用 performSyncWorkOnFiber/performConcurrentWorkOnFiber;