React中的update和updateQueue
1 )概述
- 在
ReactDOM.render
过程中,还需要创建一个 update 对象 -
update
用于记录组件状态的改变的一个对象,它存放于Fiber对象的updateQueue
中 -
updateQueue
,它是一个单向链表的结构,一次整体的更新过程当中 - 可能在这个queue里会存在多 Update
- 在这次更新的过程当中,会根据这些 update 的结果算出最终的一个新的state的节果
- 多个 update 可以同时存在,比如说我们有一个事件里面,连续调用了三次
setState
- 这三次操作产生的是三个 update,并不会每次
setState
,就更新一下整个应用 - 而是会等3个setState执行完, 3个update创建完, 放到
updateQueue
里面,然后再进行更新的操作 - 这个涉及到后续的调度的过程,以及
batchUpdates
的原理, 这个暂时不管
2 )源码
现在来看下 update 如何去创建以及它的一个数据结构
参考: https://github.com/facebook/react/blob/v16.6.0/packages/react-reconciler/src/ReactFiberReconciler.js文章来源:https://www.toymoban.com/news/detail-784486.html
找到 scheduleRootUpdate
函数文章来源地址https://www.toymoban.com/news/detail-784486.html
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback: ?Function,
) {
if (__DEV__) {
if (
ReactCurrentFiber.phase === 'render' &&
ReactCurrentFiber.current !== 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(ReactCurrentFiber.current.type) || 'Unknown',
);
}
}
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;
}
enqueueUpdate(current, update);
scheduleWork(current, expirationTime);
return expirationTime;
}
- 可见,update的创建过程,
const update = createUpdate(expirationTime);
, 进入到createUpdate
函数import {createUpdate, enqueueUpdate} from './ReactUpdateQueue';
- 找到 ReactUpdateQueue.js 文件,定位
createUpdate
函数export function createUpdate(expirationTime: ExpirationTime): Update<*> { return { // 对应这一次创建的更新的过期时间, expirationTime: expirationTime, // tag 对应4种情况 // export const UpdateState = 0; // 更新 state // export const ReplaceState = 1; // 替代 state // export const ForceUpdate = 2; // 强制更新 // export const CaptureUpdate = 3; // 渲染过程中,如果有错误被捕获,会生成一个 update, 让我们重新渲染节点的方式, Error Boundary 在组件内部捕获渲染的错误的一个更新 // 指定更新的类型,值为以上几种:0, 1, 2, 3 tag: UpdateState, // 更新内容, 对应实际执行的操作内容,比如,在上述 createUpdate中的 update.payload = {element}; 这里是把整个传进去的App, ReactElement 及其整棵树 渲染到 DomRoot 里面 // 初始渲染的 payload 就如上述 // 如果是更新,比如 setState 接收的第一个参数,可能是一个对象或方法作为 payload payload: null, callback: null, // next 对应下一个 update, 因为,update 都是存放在 updateQueue 中的, 而 updateQueue 是一个单项列表的结构 // 每个 update 都有一个 next, 在 UpdateQueue 中有一个 firstUpdate 和 lastUpdate 记录的是单项列表的开头和结尾 // 从开头到结尾都是通过 next 串联起来的,把整个update 单链表的结构连接起来 // 通过拿到 firstUpdate 基于next一层层找到 lastUpdate next: null, // 指向下一个 side effect nextEffect: null, }; }
- 同样定位到
UpdateQueue
里面export type UpdateQueue<State> = { // 每次更新应用渲染完成后,调用 UpdateQueue 计算出一个新的state 存放在 baseState 上 // 下一次节点有更新,产生了一个更新的队列,在计算新的state时,会在 baseState 基础上计算 baseState: State, // firstUpdate 和 lastUpdate 记录单项链表的数据结构 // 队列中的第一个 Update firstUpdate: Update<State> | null, // 队列中的最后一个 Update lastUpdate: Update<State> | null, // 下面两个也是记录单项链表的数据结构,只不过对应有错误捕获的时候的 Update firstCapturedUpdate: Update<State> | null, lastCapturedUpdate: Update<State> | null, // 第一个 side effect firstEffect: Update<State> | null, // 最后一个 side effect lastEffect: Update<State> | null, // 第一个和最后一个 捕获产生的 side effect firstCapturedEffect: Update<State> | null, lastCapturedEffect: Update<State> | null, };
- update 和 updateQueue 数据结构相对来说是比较简单的
- 在
createUpdate
内部要调用一个enqueueUpdate
, 如果在 setState 和 forceUpdate 里面的操作 - 去创建update时,需要调用
enqueueUpdate
,进入这个函数export function enqueueUpdate<State>(fiber: Fiber, update: Update<State>) { // Update queues are created lazily. const alternate = fiber.alternate; let queue1; let queue2; if (alternate === null) { // There's only one fiber. queue1 = fiber.updateQueue; queue2 = null; if (queue1 === null) { queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); } } else { // There are two owners. queue1 = fiber.updateQueue; queue2 = alternate.updateQueue; if (queue1 === null) { if (queue2 === null) { // Neither fiber has an update queue. Create new ones. queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState); queue2 = alternate.updateQueue = createUpdateQueue( alternate.memoizedState, ); } else { // Only one fiber has an update queue. Clone to create a new one. queue1 = fiber.updateQueue = cloneUpdateQueue(queue2); } } else { if (queue2 === null) { // Only one fiber has an update queue. Clone to create a new one. queue2 = alternate.updateQueue = cloneUpdateQueue(queue1); } else { // Both owners have an update queue. } } } if (queue2 === null || queue1 === queue2) { // There's only a single queue. appendUpdateToQueue(queue1, update); } else { // There are two queues. We need to append the update to both queues, // while accounting for the persistent structure of the list — we don't // want the same update to be added multiple times. if (queue1.lastUpdate === null || queue2.lastUpdate === null) { // One of the queues is not empty. We must add the update to both queues. appendUpdateToQueue(queue1, update); appendUpdateToQueue(queue2, update); } else { // Both queues are non-empty. The last update is the same in both lists, // because of structural sharing. So, only append to one of the lists. appendUpdateToQueue(queue1, update); // But we still need to update the `lastUpdate` pointer of queue2. queue2.lastUpdate = update; } } if (__DEV__) { if ( fiber.tag === ClassComponent && (currentlyProcessingQueue === queue1 || (queue2 !== null && currentlyProcessingQueue === queue2)) && !didWarnUpdateInsideUpdate ) { warningWithoutStack( false, 'An update (setState, replaceState, or forceUpdate) was scheduled ' + 'from inside an update function. Update functions should be pure, ' + 'with zero side-effects. Consider using componentDidUpdate or a ' + 'callback.', ); didWarnUpdateInsideUpdate = true; } } }
-
const alternate = fiber.alternate;
先读取 alternate,这个就是 current 到 workInProgress 的映射关系 - 在这里就要确保 current 和 workInProgress 对应的 updateQueue 是相同的
- 在这里会统一被处理,在这里,参数 fiber 是 current, alternate 是 workInProgress
- 先判断 alternate 是否存在, 不存在处理 queue1 和 queue2, 进入
createUpdateQueue
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> { const queue: UpdateQueue<State> = { baseState, firstUpdate: null, lastUpdate: null, firstCapturedUpdate: null, lastCapturedUpdate: null, firstEffect: null, lastEffect: null, firstCapturedEffect: null, lastCapturedEffect: null, }; return queue; }
- 生成的 UpdateQueue 就是之前看过的这个数据结构
- 接着往下的判断中进入
appendUpdateToQueue
function appendUpdateToQueue<State>( queue: UpdateQueue<State>, update: Update<State>, ) { // Append the update to the end of the list. if (queue.lastUpdate === null) { // Queue is empty queue.firstUpdate = queue.lastUpdate = update; } else { queue.lastUpdate.next = update; // 当前最后(后面会变成倒数第二位元素)的next指向update queue.lastUpdate = update; // queue的lastUpdate 指向 update } }
- 如果
lastUpdate
不存在,则说明 Queue 是空的,则进行处理 - 如果存在,进行继续如上
enqueueUpdate
中的 if else 的处理
- 如果
- 接着往下 都是这类逻辑,具体逻辑都在上面
enqueueUpdate
源码中 - 总体来讲是初始化 Fiber 上面的
updateQueue
, 以及进行更新
-
- 它接收的两个参数,Fiber 和 Update 对象
- Fiber 对象是在 创建 update时用到的
Fiber
- 对应
ReactDOM.render
就是我们的RootFiber
- 就是我们
FiberRoot
的current
- Fiber 对象是在 创建 update时用到的
- 在调用
scheduleRootUpdate
函数的updateContainerAtExpirationTime
函数中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); }
-
const current = container.current;
container.current 就对应一个 Fiber 对象
-
到了这里,关于React16源码: React中的update和updateQueue的源码实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!