[React](https://github.com/facebook/react)可大致分為三部分:Core、Reconciler和Renderer,在閱讀源碼之前,首先需要搭建測試環境,為了方便起見,本文直接采用了[網友搭建好的環境](https://github.com/pws019/react-sourcecode-debug-env),React版本是16.8.6,與最新版本很接近。
## 一、目錄結構
  React采用了由[Lerna](https://lerna.js.org/)維護monorepo方式進行代碼管理,即用一個倉庫管理多個模塊(module)或包(package)。在React倉庫的根目錄中,包含三個目錄:
  (1)fixtures,給源碼貢獻者準備的測試用例。
  (2)packages,React庫提供的包的源碼,包括核心代碼、矢量圖形庫等,如下所列。
~~~
├── packages ------------------------------------ 源碼目錄
│ ├── react-art ------------------------------- 矢量圖形渲染器
│ ├── react-dom ------------------------------- DOM渲染器
│ ├── react-native-renderer ------------------- Native渲染器(原生iOS和Android視圖)
│ ├── react-test-renderer --------------------- JSON樹渲染器
│ ├── react-reconciler ------------------------ React調和器
~~~
  (3)scripts,相關的工具配置腳本,包括語法規則、Git鉤子等。
  React使用的前端模塊化打包工具是[Rollup](https://www.rollupjs.com/),在源碼中還引入了[Flow](https://zhenyong.github.io/flowtype/),用于靜態類型檢查,在運行代碼之前發現一些潛在的問題,其語法類似于TypeScript。
## 二、React核心對象
  在項目中引入React通常是像下面這樣。
~~~
import React from 'react';
~~~
  其實引入的是核心入口文件“[packages/react/index.js](https://github.com/facebook/react/blob/master/packages/react/index.js)”中導出的對象,如下所示,其中React.default用于Jest測試,React用于Rollup。
~~~
const React = require('./src/React');
// TODO: decide on the top-level export form.
// This is hacky but makes it work with both Rollup and Jest.
module.exports = React.default || React;
~~~
  順著require()語句可以找到[React.js](https://github.com/facebook/react/blob/master/packages/react/src/React.js)中的React對象,代碼省略了一大堆導入語句,其中\_\_DEV\_\_是個全局變量,用于管理開發環境中運行的代碼塊。
~~~
const React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
lazy,
memo,
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
Fragment: REACT_FRAGMENT_TYPE,
Profiler: REACT_PROFILER_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
unstable_SuspenseList: REACT_SUSPENSE_LIST_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
unstable_withSuspenseConfig: withSuspenseConfig,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
if (enableFlareAPI) {
React.unstable_useResponder = useResponder;
React.unstable_createResponder = createResponder;
}
if (enableFundamentalAPI) {
React.unstable_createFundamental = createFundamental;
}
if (enableJSXTransformAPI) {
if (__DEV__) {
React.jsxDEV = jsxWithValidation;
React.jsx = jsxWithValidationDynamic;
React.jsxs = jsxWithValidationStatic;
} else {
React.jsx = jsx;
React.jsxs = jsx;
}
}
export default React;
~~~
  在React對象中包含了開放的[核心API](https://zh-hans.reactjs.org/docs/react-api.html),例如React.Component、React.createRef()等,以及新引入的Hooks(內部的具體邏輯可轉移到相關的包中),但渲染的邏輯已經剝離出來。
**1)React.createElement()**
  JSX中的元素稱為[React元素](http://www.hmoore.net/pwstrick/fe-questions/1167731),分為兩種類型:DOM元素和組件元素。用JSX描述的組件都會通過Babel編譯器將它們轉換成React.createElement()方法,它包含三個參數(如下所示),其中type是元素類型,也就是它的名稱;props是一個由元素屬性組成的對象;children是它的子元素(即內容),可以是文本也可以是其它元素。
~~~
React.createElement(type, [props], [...children])
~~~
  方法的返回值是一個ReactElement,省略了開發環境中的代碼。
~~~
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
props: props,
_owner: owner //記錄創建該元素的組件
};
return element;
};
~~~
  (1)$$typeof標識該對象是一個ReactElement。
  (2)當ReactElement是DOM元素時,type是元素名稱;當ReactElement是組件元素時,type是其構造函數。
  (3)[key](https://zh-hans.reactjs.org/docs/lists-and-keys.html)和[ref](https://zh-hans.reactjs.org/docs/forwarding-refs.html)是React組件中的兩個特殊屬性,前者用于標識身份,后者用于訪問render()方法內生成的組件實例和DOM元素。
  (4)props是ReactElement中的屬性,包括特殊的[children屬性](https://zh-hans.reactjs.org/docs/glossary.html#propschildren)。
## 三、Reconciler
  雖然React的DOM和Native兩種渲染器內部實現的區別很大,但為了能共享自定義組件、State、生命周期等特性,做到跨平臺,就需要共享一些邏輯,而這些邏輯由Reconciler統一處理,其中協調算法(Diffing算法)也要盡可能相似。
**1)Diffing算法**
  當調用React的render()方法時,會創建一棵由React元素組成的樹。在下一次State或Props更新時,相同的render()方法會返回一棵不同的樹。React會應用Diffing算法來高效的比較兩棵樹,算法過程如下。
  (1)當根節點為不同類型的元素時,React會拆卸原有的樹,銷毀對應的DOM節點和關聯的State、卸載子組件,最后再創建新的樹。
  (2)當比對兩個相同類型的DOM元素時,會保留DOM節點,僅比對變更的屬性。
  (3)當比對兩個相同類型的組件元素時,組件實例保持不變,更新該組件實例的Props。
  (4)當遞歸DOM節點的子元素時,React會同時遍歷兩個子元素的列表,比對相同位置的元素,性能比較低效。
  (5)在給子元素添加唯一標識的key屬性后,就能只比對變更了key屬性的元素。
**2)Fiber Reconciler**
  JavaScript與樣式計算、界面布局等各種繪制,一起運行在瀏覽器的主線程中,當JavaScript運行時間過長時,將占用整個線程,阻塞其它任務。為了能在React渲染期間回到主線程執行其它任務,在React v16中提出了Fiber Reconciler,并將其設為默認的Reconciler,解決了過去Stack Reconciler中的固有問題和遺留的痛點,提高了動畫、布局和手勢等領域的性能。Fiber Reconciler的主要目標是:
  (1)暫停和切分渲染任務,并將分割的任務分布到各個幀中。
  (2)調整優先級,并重置或復用已完成的任務。
  (3)在父子元素之間交錯處理,以支持React中的布局。
  (4)在render()方法中返回多個元素。
  (5)更好地支持錯誤邊界。
**3)調度任務**
  Fiber可以分解任務,根據優先級將任務調度到瀏覽器提供的兩個全局函數中,如下所列。
  (1)[requestAnimationFrame](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestAnimationFrame):在下一個動畫幀上執行高優先級的任務。
  (2)[requestIdleCallback](https://developer.mozilla.org/zh-CN/docs/Web/API/Window/requestIdleCallback):在線程空閑時執行低優先級的任務。
  當網頁保持在每秒60幀(1幀約為16ms)時,整體會變得很流暢。在每個幀中調用requestAnimationFrame()執行高優先級的任務;而在兩個幀之間會有一小段空閑時間,此時可執行requestIdleCallback()中的任務,該函數包含一個deadline參數(截止時間),用于切分長任務。
**4)Fiber數據結構**
  在調和期間,從render()方法得到的每個React元素都需要升級為Fiber節點,并添加到Fiber節點樹中。而與React元素不同,Fiber節點可復用,不會在每次渲染時重新創建。Fiber的數據結構大致如下,省略了部分屬性,源碼來自于[packages/react-reconciler/src/ReactFiber.js](https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiber.js)。
~~~
export type Fiber = {
tag: WorkTag,
key: null | string,
elementType: any,
type: any,
stateNode: any,
return: Fiber | null,
child: Fiber | null,
sibling: Fiber | null,
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
effectTag: SideEffectTag,
nextEffect: Fiber | null,
firstEffect: Fiber | null,
lastEffect: Fiber | null,
expirationTime: ExpirationTime,
alternate: Fiber | null,
...
};
~~~
  return、child和sibling三個屬性分別表示父節點、第一個子節點和兄弟節點,通過它們使得Fiber節點能夠基于鏈表連接在一起。假設有個ClickCounter組件,包含和兩個元素,它們三者之間的關系如圖12所示。
~~~
class ClickCounter extends React.Component {
render() {
return [
<button>Update counter</button>,
<span>10</span>
];
}
}
~~~
:-: 
圖 12 節點關系
  使用alternate屬性雙向連接當前Fiber和正在處理的Fiber(workInProgress),如下代碼所示,當需要恢復時,可通過alternate屬性直接回退。
~~~
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress.alternate = current;
current.alternate = workInProgress;
}
~~~
  到期時間(ExpirationTime)是指完成此任務的時間,該時間越短,則優先級越高,需要盡早執行,具體邏輯在同目錄的[ReactFiberExpirationTime.js](https://github.com/facebook/react/blob/master/packages/react-reconciler/src/ReactFiberExpirationTime.js)中。
## 四、生命周期鉤子方法
  React在內部執行時會分為兩個階段:render和commit。
  在第一個render階段(phase)中,React持有標記了副作用(side effect)的Fiber樹并將其應用于實例,該階段不會發生用戶可見的更改,并且可異步執行,下面列出的是在render階段執行的生命周期鉤子方法
  (1)\[UNSAFE\_\]componentWillMount(棄用)
  (2)\[UNSAFE\_\]componentWillReceiveProps(棄用)
  (3)getDerivedStateFromProps
  (4)shouldComponentUpdate
  (5)\[UNSAFE\_\]componentWillUpdate(棄用)
  (6)render
  標有UNSAFE的生命周期有可能被執行多次,并且經常被誤解和濫用,例如在這些方法中執行副作用代碼,可能出現渲染問題,或者任意操作DOM,可能引起回流(reflow)。于是官方推出了靜態的getDerivedStateFromProps()方法,可限制狀態更新以及DOM操作。
  在第二個commit階段,任務都是同步執行的,下面列出的是commit階段執行的生命周期鉤子方法,這些方法都只執行一次,其中getSnapshotBeforeUpdate()是新增的,用于替換componentWillUpdate()。
  (1)getSnapshotBeforeUpdate
  (2)componentDidMount
  (3)componentDidUpdate
  (4)componentWillUnmount
  新的流程將變成圖13這樣。
:-: 
圖 13 新的流程
*****
> 原文出處:
[博客園-React躬行記](https://www.cnblogs.com/strick/category/1455720.html)
[知乎專欄-React躬行記](https://zhuanlan.zhihu.com/pwreact)
已建立一個微信前端交流群,如要進群,請先加微信號freedom20180706或掃描下面的二維碼,請求中需注明“看云加群”,在通過請求后就會把你拉進來。還搜集整理了一套[面試資料](https://github.com/pwstrick/daily),歡迎瀏覽。

推薦一款前端監控腳本:[shin-monitor](https://github.com/pwstrick/shin-monitor),不僅能監控前端的錯誤、通信、打印等行為,還能計算各類性能參數,包括 FMP、LCP、FP 等。
- ES6
- 1、let和const
- 2、擴展運算符和剩余參數
- 3、解構
- 4、模板字面量
- 5、對象字面量的擴展
- 6、Symbol
- 7、代碼模塊化
- 8、數字
- 9、字符串
- 10、正則表達式
- 11、對象
- 12、數組
- 13、類型化數組
- 14、函數
- 15、箭頭函數和尾調用優化
- 16、Set
- 17、Map
- 18、迭代器
- 19、生成器
- 20、類
- 21、類的繼承
- 22、Promise
- 23、Promise的靜態方法和應用
- 24、代理和反射
- HTML
- 1、SVG
- 2、WebRTC基礎實踐
- 3、WebRTC視頻通話
- 4、Web音視頻基礎
- CSS進階
- 1、CSS基礎拾遺
- 2、偽類和偽元素
- 3、CSS屬性拾遺
- 4、浮動形狀
- 5、漸變
- 6、濾鏡
- 7、合成
- 8、裁剪和遮罩
- 9、網格布局
- 10、CSS方法論
- 11、管理后臺響應式改造
- React
- 1、函數式編程
- 2、JSX
- 3、組件
- 4、生命周期
- 5、React和DOM
- 6、事件
- 7、表單
- 8、樣式
- 9、組件通信
- 10、高階組件
- 11、Redux基礎
- 12、Redux中間件
- 13、React Router
- 14、測試框架
- 15、React Hooks
- 16、React源碼分析
- 利器
- 1、npm
- 2、Babel
- 3、webpack基礎
- 4、webpack進階
- 5、Git
- 6、Fiddler
- 7、自制腳手架
- 8、VSCode插件研發
- 9、WebView中的頁面調試方法
- Vue.js
- 1、數據綁定
- 2、指令
- 3、樣式和表單
- 4、組件
- 5、組件通信
- 6、內容分發
- 7、渲染函數和JSX
- 8、Vue Router
- 9、Vuex
- TypeScript
- 1、數據類型
- 2、接口
- 3、類
- 4、泛型
- 5、類型兼容性
- 6、高級類型
- 7、命名空間
- 8、裝飾器
- Node.js
- 1、Buffer、流和EventEmitter
- 2、文件系統和網絡
- 3、命令行工具
- 4、自建前端監控系統
- 5、定時任務的調試
- 6、自制短鏈系統
- 7、定時任務的進化史
- 8、通用接口
- 9、微前端實踐
- 10、接口日志查詢
- 11、E2E測試
- 12、BFF
- 13、MySQL歸檔
- 14、壓力測試
- 15、活動規則引擎
- 16、活動配置化
- 17、UmiJS版本升級
- 18、半吊子的可視化搭建系統
- 19、KOA源碼分析(上)
- 20、KOA源碼分析(下)
- 21、花10分鐘入門Node.js
- 22、Node環境升級日志
- 23、Worker threads
- 24、低代碼
- 25、Web自動化測試
- 26、接口攔截和頁面回放實驗
- 27、接口管理
- 28、Cypress自動化測試實踐
- 29、基于Electron的開播助手
- Node.js精進
- 1、模塊化
- 2、異步編程
- 3、流
- 4、事件觸發器
- 5、HTTP
- 6、文件
- 7、日志
- 8、錯誤處理
- 9、性能監控(上)
- 10、性能監控(下)
- 11、Socket.IO
- 12、ElasticSearch
- 監控系統
- 1、SDK
- 2、存儲和分析
- 3、性能監控
- 4、內存泄漏
- 5、小程序
- 6、較長的白屏時間
- 7、頁面奔潰
- 8、shin-monitor源碼分析
- 前端性能精進
- 1、優化方法論之測量
- 2、優化方法論之分析
- 3、瀏覽器之圖像
- 4、瀏覽器之呈現
- 5、瀏覽器之JavaScript
- 6、網絡
- 7、構建
- 前端體驗優化
- 1、概述
- 2、基建
- 3、后端
- 4、數據
- 5、后臺
- Web優化
- 1、CSS優化
- 2、JavaScript優化
- 3、圖像和網絡
- 4、用戶體驗和工具
- 5、網站優化
- 6、優化閉環實踐
- 數據結構與算法
- 1、鏈表
- 2、棧、隊列、散列表和位運算
- 3、二叉樹
- 4、二分查找
- 5、回溯算法
- 6、貪心算法
- 7、分治算法
- 8、動態規劃
- 程序員之路
- 大學
- 2011年
- 2012年
- 2013年
- 2014年
- 項目反思
- 前端基礎學習分享
- 2015年
- 再一次項目反思
- 然并卵
- PC網站CSS分享
- 2016年
- 制造自己的榫卯
- PrimusUI
- 2017年
- 工匠精神
- 2018年
- 2019年
- 前端學習之路分享
- 2020年
- 2021年
- 2022年
- 2023年
- 2024年
- 日志
- 2020