ReactDom.render 源碼閱讀

React16 源碼簡(jiǎn)介

React16 重寫了核心算法 reconciler。因?yàn)?JavaScript 引擎線程和 UI 渲染線程雖然不是一個(gè)線程世分,但二者是互斥的(因?yàn)镴S運(yùn)行結(jié)果會(huì)影響到UI線程的結(jié)果)孽江,當(dāng)瀏覽器在處理 JavaScript 時(shí)绘搞,頁(yè)面就會(huì)停止渲染喂击,一旦 JavaScript 執(zhí)行占用的時(shí)間過長(zhǎng)昼榛,留給 UI 渲染的時(shí)間就會(huì)縮短税迷,從而造成頁(yè)面每秒渲染的幀數(shù)過低永丝,導(dǎo)致頁(yè)面產(chǎn)生明顯的卡頓感。

React 源碼中 package 下的幾個(gè)關(guān)鍵模塊:

  • events:事件系統(tǒng)箭养。
  • react:定義節(jié)點(diǎn)及其表現(xiàn)行為的包慕嚷,代碼量很少,JSX 依賴該模塊。
  • react-dom:與更新和渲染相關(guān)喝检,取決于平臺(tái)(react-dom 和 react-native 中可能不同)嗅辣,依賴 react-reconciler、schedule挠说。

ReactDom.render

前言

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

ReactDom.render 的第一個(gè)參數(shù)為 ReactElement澡谭,那么 element、component 和 instance 的關(guān)系是什么呢损俭?

element:一個(gè)可以描述 DOM 節(jié)點(diǎn)或者 component 實(shí)例的對(duì)象蛙奖,可以通過 React.createElement() 或者 JSX 語(yǔ)法創(chuàng)建。
component:可以通過 class 或者 function 聲明杆兵。
instance:只有類組件才有實(shí)例雁仲,React 會(huì)自動(dòng)創(chuàng)建實(shí)例。

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, // 根節(jié)點(diǎn)
    callback: ?Function, // 渲染完執(zhí)行的回調(diào)
  ) {
    return legacyRenderSubtreeIntoContainer(
      null,
      element,
      container,
      false,
      callback,
    );
  }

2.創(chuàng)建 root拧咳,然后調(diào)用 root.render伯顶,最終返回 DOM 節(jié)點(diǎn)信息

// 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
    // 創(chuàng)建 root
    root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
      container,
      forceHydrate, // 在 render 中是 false骆膝,hydrate 中為 true祭衩,決定是否復(fù)用 DOM 節(jié)點(diǎn)
    );
    if (typeof callback === 'function') {
     // 執(zhí)行回調(diào)
    }
    // Initial mount should not be batched.
    // 不使用 batchedUpdates,因?yàn)槭状武秩拘枰M快完成
    unbatchedUpdates(() => {
      if (parentComponent != null) {
        // ...
      } else {
        // render 中 parentComponent = null
        root.render(children, callback);
      }
    });
  } else {
    // root 存在的情況
  }
  return getPublicRootInstance(root._internalRoot);
}

2.1 創(chuàng)建 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 對(duì)象
  return createFiberRoot(containerInfo, isConcurrent, hydrate);
}

2.2 調(diào)用 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,
) {
  // 創(chuàng)建更新
  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;
  }
  // 將更新放入隊(duì)列
  enqueueUpdate(current, update);
  // 開始調(diào)度阅签。React16+提出了任務(wù)優(yōu)先級(jí)的概念掐暮。
  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 返回真實(shí) DOM

HostComponent:抽象節(jié)點(diǎn),是 ClassComponent 的組成部分政钟。具體的實(shí)現(xiàn)取決于 React 運(yùn)行的平臺(tái)路克。在瀏覽器環(huán)境下就代表 DOM 節(jié)點(diǎn)。
Fiber:Fiber 是一個(gè)對(duì)象养交,表征 reconciliation 階段所能拆分的最小工作單元精算,和 React Instance 一一對(duì)應(yīng)。并通過 stateNode 屬性管理 Instance 的 local state碎连。

// packages/react-reconciler/src/ReactFiberReconciler.js
function getPublicRootInstance(
  container: OpaqueRoot,
): React$Component<any, any> | PublicInstance | null {
  // container.current 為 container 對(duì)應(yīng)的 Fiber
  const containerFiber = container.current;
  if (!containerFiber.child) {
    return null;
  }
  // 判斷 Fiber 子節(jié)點(diǎn)的類型
  switch (containerFiber.child.tag) {
    // 真實(shí) DOM 如 div灰羽, span 等
    case HostComponent:
      return getPublicInstance(containerFiber.child.stateNode);
    default:
      // stateNode 是跟當(dāng)前 Fiber 相關(guān)聯(lián)的本地狀態(tài)(比如瀏覽器環(huán)境就是真實(shí)的 DOM 節(jié)點(diǎn))
      return containerFiber.child.stateNode;
  }
}

function getPublicInstance(instance: Instance): * {
  return instance;
}
參考文獻(xiàn)

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市鱼辙,隨后出現(xiàn)的幾起案子廉嚼,更是在濱河造成了極大的恐慌,老刑警劉巖倒戏,帶你破解...
    沈念sama閱讀 223,207評(píng)論 6 521
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件怠噪,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡杜跷,警方通過查閱死者的電腦和手機(jī)傍念,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,455評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門矫夷,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人捂寿,你說(shuō)我怎么就攤上這事口四。” “怎么了秦陋?”我有些...
    開封第一講書人閱讀 170,031評(píng)論 0 366
  • 文/不壞的土叔 我叫張陵蔓彩,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我驳概,道長(zhǎng)赤嚼,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,334評(píng)論 1 300
  • 正文 為了忘掉前任顺又,我火速辦了婚禮更卒,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘稚照。我一直安慰自己蹂空,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,322評(píng)論 6 398
  • 文/花漫 我一把揭開白布果录。 她就那樣靜靜地躺著上枕,像睡著了一般。 火紅的嫁衣襯著肌膚如雪弱恒。 梳的紋絲不亂的頭發(fā)上辨萍,一...
    開封第一講書人閱讀 52,895評(píng)論 1 314
  • 那天,我揣著相機(jī)與錄音返弹,去河邊找鬼锈玉。 笑死,一個(gè)胖子當(dāng)著我的面吹牛义起,可吹牛的內(nèi)容都是我干的拉背。 我是一名探鬼主播,決...
    沈念sama閱讀 41,300評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼默终,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼去团!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起穷蛹,我...
    開封第一講書人閱讀 40,264評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎昼汗,沒想到半個(gè)月后肴熏,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,784評(píng)論 1 321
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡顷窒,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,870評(píng)論 3 343
  • 正文 我和宋清朗相戀三年蛙吏,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了源哩。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,989評(píng)論 1 354
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鸦做,死狀恐怖励烦,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情泼诱,我是刑警寧澤坛掠,帶...
    沈念sama閱讀 36,649評(píng)論 5 351
  • 正文 年R本政府宣布,位于F島的核電站治筒,受9級(jí)特大地震影響屉栓,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜耸袜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,331評(píng)論 3 336
  • 文/蒙蒙 一友多、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧堤框,春花似錦域滥、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,814評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至资昧,卻和暖如春酬土,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背格带。 一陣腳步聲響...
    開封第一講書人閱讀 33,940評(píng)論 1 275
  • 我被黑心中介騙來(lái)泰國(guó)打工撤缴, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人叽唱。 一個(gè)月前我還...
    沈念sama閱讀 49,452評(píng)論 3 379
  • 正文 我出身青樓屈呕,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親棺亭。 傳聞我的和親對(duì)象是個(gè)殘疾皇子虎眨,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,995評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容

  • 前言 Facebook 的研發(fā)能力真是驚人, Fiber 架構(gòu)給 React 帶來(lái)了新視野的同時(shí)镶摘,將調(diào)度一詞介紹給...
    Floveluy閱讀 1,773評(píng)論 1 5
  • 在《JavaScript異步機(jī)制》這篇文章中我們說(shuō)到嗽桩,Js引擎是單線程的,它負(fù)責(zé)維護(hù)任務(wù)棧凄敢,并通過 Event L...
    tobAlier閱讀 2,989評(píng)論 3 6
  • 原教程內(nèi)容詳見精益 React 學(xué)習(xí)指南碌冶,這只是我在學(xué)習(xí)過程中的一些閱讀筆記,個(gè)人覺得該教程講解深入淺出涝缝,比目前大...
    leonaxiong閱讀 2,843評(píng)論 1 18
  • 今天我們要做風(fēng)車扑庞,我要準(zhǔn)備:一張正方形的彩紙譬重、一把剪刀、一圈膠布罐氨、一根牙簽臀规。 首先,拿出一張正方形的紙...
    浚皓閱讀 667評(píng)論 0 1
  • 是夜栅隐,余隨愛考拉青島夏令營(yíng)至靈山島小靈山露營(yíng)塔嬉。眾人支帳既罷,已而人定约啊。余無(wú)倦意邑遏,遂與濤兄和衣臥于海畔高崖之上,枕海...
    西祠夢(mèng)天閱讀 591評(píng)論 5 14