在 react15 的 diff 算法里是一气呵成的更新 state,一旦有动画还有太大的 state 就会造成卡顿,不停的循环 Dom 树
{f:'div',type:'dom',props:{},children:[{[...]},{[...]},{[...]}]}
在 react16 中的 diff 算法 fiber,fiber 最大的改动就是讲数组结构改成了列表结构,在 fiber 中使用了 window.requestIdleCallback 来优化 diff 算法
{f:'div',type:'dom',props:{},next:'x86dasa',pre:'vdasd98'}
//其实就是使用索引指向了一个内存地址
在 react16 之前采用的算法,父组件里调子组件,可以类比为函数的递归,在 setState 后,react 会立即开始 reconciliation 过程,从父节点(Virtual DOM)开始遍历,以找出不同。将所有的 Virtual DOM 遍历完成后,reconciler 才能给出当前需要修改真实 DOM 的信息,并传递给 renderer,进行渲染,然后屏幕上才会显示此次更新内容。对于特别庞大的 vDOM 树来说,reconciliation 过程会很长(x00ms),在这期间,主线程是被 js 占用的,因此任何交互、布局、渲染都会停止,给用户的感觉就是页面被卡住了。
大家应该都清楚进程(Process)和线程(Thread)的概念,在计算机科学中还有一个概念叫做 Fiber,英文含义就是“纤维”,意指比 Thread 更细的线,也就是比线程(Thread)控制得更精密的并发处理机制。
在看源码之前,先附上 react Fiber 的神图,我也是照着图跟着函数看的
const ReactDOM: Object = {
hydrate(element: React$Node, container: DOMContainer, callback: ?Function) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
warningWithoutStack(
!container._reactHasBeenPassedToCreateRootDEV,
'You are calling ReactDOM.hydrate() on a container that was previously ' +
'passed to ReactDOM.%s(). This is not supported. ' +
'Did you mean to call createRoot(container, {hydrate: true}).render(element)?',
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
);
}
// TODO: throw or warn if we couldn't hydrate?
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
},
render(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
) {
invariant(
isValidContainer(container),
'Target container is not a DOM element.',
);
if (__DEV__) {
warningWithoutStack(
!container._reactHasBeenPassedToCreateRootDEV,
'You are calling ReactDOM.render() on a container that was previously ' +
'passed to ReactDOM.%s(). This is not supported. ' +
'Did you mean to call root.render(element)?',
enableStableConcurrentModeAPIs ? 'createRoot' : 'unstable_createRoot',
);
}
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
},
//此处暂时省略其他代码。。。
}
我们可以看到,hydrate 和 render 都返回了一个 legacyRenderSubtreeIntoContainer 函数,那么 legacyRenderSubtreeIntoContainer 函数做了什么
function legacyRenderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
if (__DEV__) {
topLevelUpdateWarnings(container);
}
// TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
let root: Root = (container._reactRootContainer: any);
// 第一次root 不存在
if (!root) {
// Initial mount
// 创建reactRoot,在dom元素上挂载,
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Initial mount should not be batched.
// 首次不用批量更新, batchedUpdate是等view更新完之后在去统一更新状态, 有一个等待的过程, 第一次不需要,可以更快
unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
// ReactRoot.prototype.render
root.render(children, callback);
}
});
} else {
if (typeof callback === 'function') {
const originalCallback = callback;
callback = function() {
const instance = getPublicRootInstance(root._internalRoot);
originalCallback.call(instance);
};
}
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
// ReactRoot.prototype.render
root.render(children, callback);
}
}
return getPublicRootInstance(root._internalRoot);
}
legacyRenderSubtreeIntoContainer 主要作用:
我们可以看到,在创建 Fiber Root 的时候调用了 legacyCreateRootFromDOMContainer 函数,我们看一下
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
// 是否需要复用节点
const shouldHydrate =
forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
// First clear any existing content.
if (!shouldHydrate) {
let warned = false;
let rootSibling;
// 删除子节点
while ((rootSibling = container.lastChild)) {
if (__DEV__) {
if (
!warned &&
rootSibling.nodeType === ELEMENT_NODE &&
(rootSibling: any).hasAttribute(ROOT_ATTRIBUTE_NAME)
) {
warned = true;
warningWithoutStack(
false,
'render(): Target node has markup rendered by React, but there ' +
'are unrelated nodes as well. This is most commonly caused by ' +
'white-space inserted around server-rendered markup.',
);
}
}
container.removeChild(rootSibling);
}
}
if (__DEV__) {
if (shouldHydrate && !forceHydrate && !warnedAboutHydrateAPI) {
warnedAboutHydrateAPI = true;
lowPriorityWarning(
false,
'render(): Calling ReactDOM.render() to hydrate server-rendered markup ' +
'will stop working in React v17. Replace the ReactDOM.render() call ' +
'with ReactDOM.hydrate() if you want React to attach to the server HTML.',
);
}
}
// Legacy roots are not async by default.
const isConcurrent = false;
// 返回一个新创建的ReactRoot
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
legacyCreateRootFromDOMContainer 函数主要判断了是否是 ssr,清除所有子元素,返回 ReactRoot 函数,在这里我们知道了,root 更新时同步的
function ReactRoot(
container: DOMContainer,
isConcurrent: boolean,
hydrate: boolean,
) {
// 创建FiberRoot
const root = createContainer(container, isConcurrent, hydrate);
// 把创建的节点绑在_internalRoot属性上
this._internalRoot = root;
}
调用 createContainer 函数,将创建的节点绑定在_internalRoot 上
export function createContainer(
containerInfo: Container,
isConcurrent: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isConcurrent, hydrate);
}
export function createFiberRoot(
containerInfo: any,
isConcurrent: boolean,
hydrate: boolean,
): FiberRoot {
// Cyclic construction. This cheats the type system right now because
// stateNode is any.
const uninitializedFiber = createHostRootFiber(isConcurrent);
let root;
if (enableSchedulerTracing) {
root = ({
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,
earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,
pingCache: null,
didError: false,
pendingCommitExpirationTime: NoWork,
finishedWork: null,
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork,
expirationTime: NoWork,
firstBatch: null,
nextScheduledRoot: null,
interactionThreadID: unstable_getThreadID(),
memoizedInteractions: new Set(),
pendingInteractionMap: new Map(),
}: FiberRoot);
} else {
root = ({
// 当前应用对应的Fiber对象
current: uninitializedFiber,
// root节点
containerInfo: containerInfo,
// 只有在持久更新中会用到
pendingChildren: null,
pingCache: null,
earliestPendingTime: NoWork,
latestPendingTime: NoWork,
earliestSuspendedTime: NoWork,
latestSuspendedTime: NoWork,
latestPingedTime: NoWork,
didError: false,
pendingCommitExpirationTime: NoWork,
// 指向当前已经完成准备工作的Fiber Tree Root, 在commit阶段处理
finishedWork: null,
// 在任务被挂起的时候通过setTimeout设置的返回内容,用来下一次如果有新的任务挂起时清理还没触发的timeout
timeoutHandle: noTimeout,
context: null,
pendingContext: null,
hydrate,
nextExpirationTimeToWorkOn: NoWork,
// 过期时间
expirationTime: NoWork,
firstBatch: null,
nextScheduledRoot: null,
}: BaseFiberRootProperties);
}
uninitializedFiber.stateNode = root;
// The reason for the way the Flow types are structured in this file,
// Is to avoid needing :any casts everywhere interaction tracing fields are used.
// Unfortunately that requires an :any cast for non-interaction tracing capable builds.
// $FlowFixMe Remove this :any cast and replace it with something better.
return ((root: any): FiberRoot);
}
我们看到,还调用了 createHostRootFiber 函数,createHostRootFiber 函数中调用了 createFiber 方法,下面看一下 createFiber 函数
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
// $FlowFixMe: the shapes are exact here but Flow doesn't like constructors
return new FiberNode(tag, pendingProps, key, mode);
};
我们发现 createFiber 函数实例化了 FiberNode
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
// FiberNode的类型
this.tag = tag;
this.key = key;
this.elementType = null;
// Function|String|Symbol|Number|Object
this.type = null;
this.stateNode = null;
// Fiber 表示父级 FiberNode
this.return = null;
// 表示第一个子 FiberNode
this.child = null;
// 表示紧紧相邻的下一个兄弟 FiberNode
this.sibling = null;
this.index = 0;
this.ref = null;
// 表示新的props
this.pendingProps = pendingProps;
// 表示经过所有流程处理后的新props
this.memoizedProps = null;
// 更新队列,队列内放着即将要发生的变更状态,详细内容后面再讲解
this.updateQueue = null;
// 表示经过所有流程处理后的新state
this.memoizedState = null;
this.contextDependencies = null;
this.mode = mode;
// Effects 可以理解为通过一个字段标识n个动作
this.effectTag = NoEffect;
// 下一个将要处理的副作用F
this.nextEffect = null;
// 第一个需要处理的副作用
this.firstEffect = null;
// 最后一个将要处理的副作用F
this.lastEffect = null;
// 过期时间
this.expirationTime = NoWork;
this.childExpirationTime = NoWork;
// 指向 自己另一个镜像
this.alternate = null;
if (enableProfilerTimer) {
this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
if (__DEV__) {
this._debugID = debugCounter++;
this._debugSource = null;
this._debugOwner = null;
this._debugIsCurrentlyTiming = false;
this._debugHookTypes = null;
if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
Object.preventExtensions(this);
}
}
}
初始化 Fiber Root 就结束了
接下来,我们再看一下上面说到的 legacyRenderSubtreeIntoContainer 里批量更新的操作
// 首次不用批量更新, batchedUpdate是等view更新完之后在去统一更新状态, 有一个等待的过程, 第一次不需要,可以更快
unbatchedUpdates(() => {
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
// ReactRoot.prototype.render
root.render(children, callback);
}
});
我们再看 legacy_renderSubtreeIntoContainer 函数
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback: ?() => mixed,
): Work {
const root = this._internalRoot;
const work = new ReactWork();
callback = callback === undefined ? null : callback;
if (__DEV__) {
warnOnInvalidCallback(callback, 'render');
}
if (callback !== null) {
work.then(callback);
}
updateContainer(children, root, parentComponent, work._onCommit);
return work;
};
里面调用了 updateContainer 函数
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): ExpirationTime {
const current = container.current;
// 通过 msToExpirationTime 得到currentTime
const currentTime = requestCurrentTime();
// 根据给任务分优先级,来得到不同的过期时间
const expirationTime = computeExpirationForFiber(currentTime, current);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
updateContainer 函数主要就是计算 Fiber 的优先级,通过 expirationTime 得到过期时间,最后调用 updateContainerAtExpirationTime 方法
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
// var ImmediatePriority = 1; 最高优先级, 直接走messageChannel 直接处理
// var UserBlockingPriority = 2;
// var NormalPriority = 3; 默认
// var LowPriority = 4;
// var IdlePriority = 5;
// 优先级越高,ExpirationTime 超时时间越低
const priorityLevel = getCurrentPriorityLevel();
let expirationTime;
if ((fiber.mode & ConcurrentMode) === NoContext) {
// Outside of concurrent mode, updates are always synchronous.
// 在并发模式之外,更新始终是同步的。
expirationTime = Sync;
// isWorking 在renderRoot或者CommitRoot
// isCommitting CommitRoot
} else if (isWorking && !isCommitting) {
// 在render阶段,优先级设置为下次渲染的到期时间
// During render phase, updates expire during as the current render.
expirationTime = nextRenderExpirationTime;
} else {
// 在commit阶段,根据priorityLevel进行expirationTime更新
switch (priorityLevel) {
case ImmediatePriority:
// 立即执行
expirationTime = Sync;
break;
case UserBlockingPriority:
// 因用户交互阻塞的优先级
expirationTime = computeInteractiveExpiration(currentTime);
break;
case NormalPriority:
// 一般,默认优先级, 异步执行
// This is a normal, concurrent update
expirationTime = computeAsyncExpiration(currentTime);
break;
case LowPriority:
case IdlePriority:
// 低优先级或空闲状态
expirationTime = Never;
break;
default:
invariant(
false,
'Unknown priority level. This error is likely caused by a bug in ' +
'React. Please file an issue.',
);
}
// If we're in the middle of rendering a tree, do not update at the same
// expiration time that is already rendering.
// 下一个fiber存在,且当前的fiber的过期时间和下一个fiber的过期时间一致
// 把当前的fiber的过期时间减1
// 避免在渲染树的时候同时去更新已经渲染的树
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) {
expirationTime -= 1;
}
}
// Keep track of the lowest pending interactive expiration time. This
// allows us to synchronously flush all interactive updates
// when needed.
// TODO: Move this to renderer?
// 记录下挂起的用户交互任务中expirationTime最短的一个,在需要时同步刷新所有交互式更新
if (
priorityLevel === UserBlockingPriority &&
(lowestPriorityPendingInteractiveExpirationTime === NoWork ||
expirationTime < lowestPriorityPendingInteractiveExpirationTime)
) {
lowestPriorityPendingInteractiveExpirationTime = expirationTime;
}
return expirationTime;
}
computeExpirationForFiber 就是计算过期时间
export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
expirationTime: ExpirationTime,
callback: ?Function,
) {
// TODO: If this is a nested container, this won't be the root.
const current = container.current;
if (__DEV__) {
if (ReactFiberInstrumentation.debugTool) {
if (current.alternate === null) {
ReactFiberInstrumentation.debugTool.onMountContainer(container);
} else if (element === null) {
ReactFiberInstrumentation.debugTool.onUnmountContainer(container);
} else {
ReactFiberInstrumentation.debugTool.onUpdateContainer(container);
}
}
}
const context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current, element, expirationTime, callback);
}
计算完优先级后,执行 updateContainerAtExpirationTime 函数,放入 scheduleRootUpdate 函数,用于更新 container
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
if (__DEV__) {
if (
ReactCurrentFiberPhase === 'render' &&
ReactCurrentFiberCurrent !== null &&
!didWarnAboutNestedUpdates
) {
didWarnAboutNestedUpdates = true;
warningWithoutStack(
false,
'Render methods should be a pure function of props and state; ' +
'triggering nested component updates from render is not allowed. ' +
'If necessary, trigger nested updates in componentDidUpdate.\n\n' +
'Check the render method of %s.',
getComponentName(ReactCurrentFiberCurrent.type) || 'Unknown',
);
}
}
// 新建一个update
// expirationTime: expirationTime,
// tag: UpdateState,
// payload: null,
// callback: null,
// next: null,
// nextEffect: null,
const update = createUpdate(expirationTime);
// Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {element};
callback = callback === undefined ? null : callback;
if (callback !== null) {
warningWithoutStack(
typeof callback === 'function',
'render(...): Expected the last optional `callback` argument to be a ' +
'function. Instead received: %s.',
callback,
);
update.callback = callback;
}
// 调用schedule的回调
flushPassiveEffects();
// 延迟创建update quenes, 并把update 更新到update quenes中
// update 添加到 current.updateQuene.firstUpdate|lastUpdate
enqueueUpdate(current, update);
scheduleWork(current, expirationTime);
return expirationTime;
}
scheduleRootUpdate 将 update 添加到 current.updateQuene 里,并且调用 scheduleWork 方法
function scheduleWork
(fiber: Fiber, expirationTime: ExpirationTime) {
// 设置expirationTime & 返回root节点的Fiber对象
const root = scheduleWorkToRoot(fiber, expirationTime);
if (root === null) {
if (__DEV__) {
switch (fiber.tag) {
case ClassComponent:
warnAboutUpdateOnUnmounted(fiber, true);
break;
case FunctionComponent:
case ForwardRef:
case MemoComponent:
case SimpleMemoComponent:
warnAboutUpdateOnUnmounted(fiber, false);
break;
}
}
return;
}
// isWorking 在render和commit两个阶段都会为true
// 新的render过期时间不是noWork
// 之前的过期时间大于现在新的过期时间
// 表达的含义:当前没有任务在执行,之前执行过任务,同时当前的任务比之前执行的任务过期时间要小
if (
!isWorking &&
// nextRenderExpirationTime 在初始的时候是noWork, 被设置后不再是noWork
nextRenderExpirationTime !== NoWork &&
// ExpirationTime时间越小, 优先级更高
expirationTime > nextRenderExpirationTime
) {
// This is an interruption. (Used for performance tracking.)
// 中断了执行 , 因为有优先级更高的任务进来了
// isWorking=false意味着,在上一个时间片执行完之后进这个判断,
// 有更高优先级的任务,则中断了之前任务的执行
interruptedBy = fiber;
// 清空之前任务的的stack
resetStack();
}
// 更新最近和最早的时间
markPendingPriorityLevel(root, expirationTime);
// 要么没有任何任务 要么有任务但处于commitRoot阶段
if (
// If we're in the render phase, we don't need to schedule this root
// for an update, because we'll do it before we exit...
!isWorking ||
isCommitting ||
// ...unless this is a different root than the one we're rendering.
nextRoot !== root
) {
const rootExpirationTime = root.expirationTime;
requestWork(root, rootExpirationTime);
}
if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
// Reset this back to zero so subsequent updates don't throw.
nestedUpdateCount = 0;
invariant(
false,
'Maximum update depth exceeded. This can happen when a ' +
'component repeatedly calls setState inside ' +
'componentWillUpdate or componentDidUpdate. React limits ' +
'the number of nested updates to prevent infinite loops.',
);
}
}
判断有无优先级更高的任务,如果没有,则执行 requestWork 方法,如果有,中断任务,清空队列,执行优先级更高的任务
function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
// 把root添加到调度队列, 链表的形式
addRootToSchedule(root, expirationTime);
// 在render过程当中, 直接返回return
// batchedUpdate 批处理 setState isRendering=true 第二个setState
// 不会出现频繁式的更新
if (isRendering) {
// Prevent reentrancy. Remaining work will be scheduled at the end of
// the currently rendering batch.
return;
}
if (isBatchingUpdates) {
// Flush work at the end of the batch.
// 执行unbatchedUpdates()时会设置为true
if (isUnbatchingUpdates) {
// ...unless we're inside unbatchedUpdates, in which case we should
// flush it now.
nextFlushedRoot = root;
nextFlushedExpirationTime = Sync;
// 立即执行更新
performWorkOnRoot(root, Sync, false);
}
// 批处理 return 不执行
return;
}
// TODO: Get rid of Sync and use current time?
// 同步任务,直接执行
if (expirationTime === Sync) {
// 立即同步执行
performSyncWork();
} else {
// 通过ExpirationTime 进行调度
scheduleCallbackWithExpirationTime(root, expirationTime);
}
}
requestWork 方法里主要判断任务状态,如果是渲染中或者是批处理,直接返回,否则,执行任务,通过判断任务的状态,执行不同的方法