ReactDom.render 源码阅读

React16 源码简介

React16 重写了核心算法 reconciler。因为 JavaScript 引擎线程和 UI 渲染线程虽然不是一个线程,但二者是互斥的(因为JS运行结果会影响到UI线程的结果),当浏览器在处理 JavaScript 时,页面就会停止渲染,一旦 JavaScript 执行占用的时间过长,留给 UI 渲染的时间就会缩短,从而造成页面每秒渲染的帧数过低,导致页面产生明显的卡顿感。

React 源码中 package 下的几个关键模块:

  • events:事件系统。
  • react:定义节点及其表现行为的包,代码量很少,JSX 依赖该模块。
  • react-dom:与更新和渲染相关,取决于平台(react-dom 和 react-native 中可能不同),依赖 react-reconciler、schedule。

ReactDom.render

前言

ReactDom.render(
    element: React$Element<any>,
    container: DOMContainer,
    callback: ?Function,
  )

ReactDom.render 的第一个参数为 ReactElement,那么 element、component 和 instance 的关系是什么呢?

element:一个可以描述 DOM 节点或者 component 实例的对象,可以通过 React.createElement() 或者 JSX 语法创建。
component:可以通过 class 或者 function 声明。
instance:只有类组件才有实例,React 会自动创建实例。

component 👇

class App extends React.Component {
  render() {
    <div>hello!</div>
  }
}

element & instance 👇

<App />

dom element 👇

<div>hello!</div>

源码阅读

reactdom.render.png

1.入口

// packages/react-dom/src/client/ReactDOM.js
render(
    element: React$Element<any>, // 要渲染的元素
    container: DOMContainer, // 根节点
    callback: ?Function, // 渲染完执行的回调
  ) {
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  }

2.创建 root,然后调用 root.render,最终返回 DOM 节点信息

// packages/react-dom/src/client/ReactDOM.js
function legacyRenderSubtreeIntoContainer(
  parentComponent: ?React$Component<any, any>, // null
  children: ReactNodeList, //element
  container: DOMContainer, //container
  forceHydrate: boolean, //false
  callback: ?Function, //callback
) {
  let root: Root = (container._reactRootContainer: any);
  // 首次渲染,root 不存在
  if (!root) {
    // Initial mount
    // 创建 root
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate, // 在 render 中是 false,hydrate 中为 true,决定是否复用 DOM 节点
    );
    if (typeof callback === 'function') {
     // 执行回调
    }
    // Initial mount should not be batched.
    // 不使用 batchedUpdates,因为首次渲染需要尽快完成
    unbatchedUpdates(() => {
      if (parentComponent != null) {
        // ...
      } else {
        // render 中 parentComponent = null
        root.render(children, callback);
      }
    });
  } else {
    // root 存在的情况
  }
  return getPublicRootInstance(root._internalRoot);
}

2.1 创建 root

function legacyCreateRootFromDOMContainer(
  container: DOMContainer,
  forceHydrate: boolean,
): Root {
  // ...
  // Legacy roots are not async by default.
  const isConcurrent = false;
  return new ReactRoot(container, isConcurrent, shouldHydrate);
}

function ReactRoot(
  container: DOMContainer,
  isConcurrent: boolean,
  hydrate: boolean,
) {
  const root = createContainer(container, isConcurrent, hydrate);
  this._internalRoot = root;
}

function createContainer(
  containerInfo: Container,
  isConcurrent: boolean,
  hydrate: boolean,
): OpaqueRoot {
  // 最终返回 FiberRoot 对象
  return createFiberRoot(containerInfo, isConcurrent, hydrate);
}

2.2 调用 root.render

ReactRoot.prototype.render = function(
  children: ReactNodeList,
  callback: ?() => mixed,
): Work {
  const root = this._internalRoot;
  const work = new ReactWork();
  callback = callback === undefined ? null : callback;
  if (callback !== null) {
    work.then(callback);
  }
  updateContainer(children, root, null, work._onCommit);
  return work;
};

function updateContainer(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  callback: ?Function,
): ExpirationTime {
  const current = container.current;
  const currentTime = requestCurrentTime();
  const expirationTime = computeExpirationForFiber(currentTime, current);
  return updateContainerAtExpirationTime(
    element,
    container,
    parentComponent,
    expirationTime,
    callback,
  );
}

function updateContainerAtExpirationTime(
  element: ReactNodeList,
  container: OpaqueRoot,
  parentComponent: ?React$Component<any, any>,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
  const current = container.current;
  const context = getContextForSubtree(parentComponent);
  if (container.context === null) {
    container.context = context;
  } else {
    container.pendingContext = context;
  }

  return scheduleRootUpdate(current, element, expirationTime, callback);
}

function scheduleRootUpdate(
  current: Fiber,
  element: ReactNodeList,
  expirationTime: ExpirationTime,
  callback: ?Function,
) {
  // 创建更新
  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) {
    update.callback = callback;
  }
  // 将更新放入队列
  enqueueUpdate(current, update);
  // 开始调度。React16+提出了任务优先级的概念。
  scheduleWork(current, expirationTime);

  return expirationTime;
}

function createUpdate(expirationTime: ExpirationTime): Update<*> {
  return {
    expirationTime: expirationTime,
    tag: UpdateState,
    payload: null,
    callback: null,
    next: null,
    nextEffect: null,
  };
}

2.3 返回真实 DOM

HostComponent:抽象节点,是 ClassComponent 的组成部分。具体的实现取决于 React 运行的平台。在浏览器环境下就代表 DOM 节点。
Fiber:Fiber 是一个对象,表征 reconciliation 阶段所能拆分的最小工作单元,和 React Instance 一一对应。并通过 stateNode 属性管理 Instance 的 local state。

// packages/react-reconciler/src/ReactFiberReconciler.js
function getPublicRootInstance(
  container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
  // container.current 为 container 对应的 Fiber
  const containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  // 判断 Fiber 子节点的类型
  switch (containerFiber.child.tag) {
    // 真实 DOM 如 div, span 等
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      // stateNode 是跟当前 Fiber 相关联的本地状态(比如浏览器环境就是真实的 DOM 节点)
      return containerFiber.child.stateNode;
  }
}

function getPublicInstance(instance: Instance): * {
  return instance;
}
参考文献

https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,372评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,368评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,415评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,157评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,171评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,125评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,028评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,887评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,310评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,533评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,690评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,411评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,004评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,812评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,693评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,577评论 2 353

推荐阅读更多精彩内容

  • 前言 Facebook 的研发能力真是惊人, Fiber 架构给 React 带来了新视野的同时,将调度一词介绍给...
    Floveluy阅读 1,764评论 1 5
  • 在《JavaScript异步机制》这篇文章中我们说到,Js引擎是单线程的,它负责维护任务栈,并通过 Event L...
    tobAlier阅读 2,985评论 3 6
  • 原教程内容详见精益 React 学习指南,这只是我在学习过程中的一些阅读笔记,个人觉得该教程讲解深入浅出,比目前大...
    leonaxiong阅读 2,833评论 1 18
  • 今天我们要做风车,我要准备:一张正方形的彩纸、一把剪刀、一圈胶布、一根牙签。 首先,拿出一张正方形的纸...
    浚皓阅读 665评论 0 1
  • 是夜,余随爱考拉青岛夏令营至灵山岛小灵山露营。众人支帐既罢,已而人定。余无倦意,遂与涛兄和衣卧于海畔高崖之上,枕海...
    西祠梦天阅读 582评论 5 14