# react解析: render的中的update(四)

感谢 yck: 剖析 React 源码解析 (opens new window),本篇文章是在读完他的文章的基础上,将他的文章进行拆解和加工,加入我自己的一下理解和例子,便于大家理解。觉得yck (opens new window)写的真的很棒 。React 版本为 16.8.6,关于源码的阅读,可以移步到yck react源码解析 (opens new window)

本文永久有效链接: react解析: render的中的update(四) (opens new window)

上一章节说到,不存在root数据节点,即通过createFiberRoot 函数创建FiberRootFiberRoot对象是整个React应用的起点,同时也记录了整个React应用更新过程中的各种信息。

下面将要聊到的就是,当root呗创建后,还会发生什么👇👇

# legacyRenderSubtreeIntoContainer 函数

下面衔接上一部分内容,不懂得可以查看上一章节

yck: ReactDOM 源码 554行 legacyRenderSubtreeIntoContainer (opens new window)

function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>,
  children: ReactNodeList,
  container: DOMContainer,
  forceHydrate: boolean,
  callback: ?Function,
) {
    // 初始化时,container 肯定没有 _reactRootContainer属性
    let root: Root = (container._reactRootContainer: any);
    if (!root) {
        // 省略创建root部分
        
        unbatchedUpdates(() => {
            if (parentComponent != null) {
                root.legacy_renderSubtreeIntoContainer(
                    parentComponent,
                    children,
                    callback,
                );
            } else {
                root.render(children, callback);
            }
        });
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

在root刚刚被创建时,parentComponent一般都为null;

unbatchedUpdates函数在这里作用是:告知React内部不进行批量更新,即不用将多个setState合并为一个; (setState在后面的章节我们将会说到

那么这里实际调用的就是root.render函数,root是ReactRoot实例对象,即调用 root.render函数 == ReactRoot.prototype.render函数

# ReactRoot.prototype.render 函数

yck: ReactRoot 源码 377行 ReactRoot.prototype.render (opens new window)

ReactRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  // 这里指 FiberRoot
  const root = this._internalRoot;
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;

  // 如果有 callback,就 push 进 work 中的数组
  if (callback !== null) {
    work.then(callback);
  }
  // work._onCommit 就是用于执行所有回调函数的
  updateContainer(children, root, null, work._onCommit);
  return work;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

函数中的参数children即是ReactElement节点对象,callback为回调函数。ReactWork实例对象的主要作用就是维护一个回调数组,可查看yck: ReactWork 源码 327行 (opens new window),如果传入参数中存在callback,就将其挂载ReactWork实例对象中;

下面来看看updateContainer函数会做什么。

# updateContainer 函数

yck: ReactFiberReconciler 源码 284行 updateContainer (opens new window)

export function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  const current = container.current;
  // 计算时间
  const currentTime = requestCurrentTime();
  // expirationTime 代表优先级,数字越大优先级越高
  // sync 的数字是最大的,所以优先级也是最高的
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

container.current即是从FiberRoot中取出RootFiber对象,currentTime就是当前距离React应用初始化的时间。 **expirationTime字面意思就是过期时间,后面我会专门花一章的时间来介绍这两个时间,这两个时间也是React应用任务调度的重点。

# scheduleRootUpdate函数

updateContainerAtExpirationTime函数实际调用的就是scheduleRootUpdate函数,下面来说一下scheduleRootUpdate函数的作用。

yck: ReactFiberReconciler 源码 114行 scheduleRootUpdate (opens new window)

function scheduleRootUpdate(
  current: Fiber,
  element: ReactNodeList,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
  // 创建一个 update,就是内部有几个属性的对象
  const update = createUpdate(expirationTime);
  update.payload = {element};

  // render中的回调函数 
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    update.callback = callback;
  }

  flushPassiveEffects();
  // 把 update 入队,内部就是一些创建或者获取 queue(链表结构),然后给链表添加一个节点的操作
  enqueueUpdate(current, update);
  scheduleWork(current, expirationTime);

  return expirationTime;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

下面就是update对象其中的属性:

// update对象属性
export type Update<State> = {
  // 更新的过期时间
  expirationTime: ExpirationTime,

  // export const UpdateState = 0;
  // export const ReplaceState = 1;
  // export const ForceUpdate = 2;
  // export const CaptureUpdate = 3;
  // 指定更新的类型,值为以上几种
  tag: 0 | 1 | 2 | 3,
  // 更新内容,比如`setState`接收的第一个参数
  payload: any,
  // 对应的回调,`setState`,`render`都有
  callback: (() => mixed) | null,

  // 指向下一个更新
  next: Update<State> | null,
  // 指向下一个`side effect`
  nextEffect: Update<State> | null,
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

udate对象会被插入到React应用维护的任务队列中,不管你是setState还是ReactDOM.render造成的 React应用 更新都是如此。这个函数核心作用就是创建或者获取一个队列,然后把 update 对象插入队列进行更新。scheduleWork函数就是任务调度的东西了。

更多内容:

react解析: React.createElement(一) (opens new window)

react解析: React.Children(二) (opens new window)

react解析: render的FiberRoot(三) (opens new window)

参考:

yck: 剖剖析 React 源码 (opens new window)

Jokcy 的 《React 源码解析》: react.jokcy.me/ (opens new window)

ps: 顺便推一下自己的个人公众号:Yopai,有兴趣的可以关注,每周不定期更新,分享可以增加世界的快乐

🥰Me

男性,武汉工作,会点Web,擅长Javascript.

技术或工作问题交流,可联系微信:1169170165.

🚀小程序&公众号
🚀运行
2024年2月19日星期一下午2点01分
博客已运行:--天--时--分--秒
上次更新: 7/18/2023, 6:00:41 PM

评 论: