react原始碼總覽(翻譯)

大~熊發表於2018-12-10

用react也有段時間了, 是時候看看人家原始碼了. 看原始碼之前看到官方文件 有這麼篇文章介紹其程式碼結構了, 為了看原始碼能順利些, 遂決定將其翻譯來看看, 小弟英語也是半瓢水, 好多單詞得查詞典, 不當之處請批評. 直接從字面翻譯的, 後面看原始碼後可能會在再修改下.


下面是翻譯

這部分將給你介紹下react程式碼的基本結構, 程式碼約定和它的基本實現.

如果你想為react貢獻程式碼的話, 我們希望這篇指南能讓你寫程式碼更加舒服.

我們不推薦將這些約定用在react應用中, 因為這些約定大多是基於一些歷史原因存在的, 隨著時間推移可能會發生變化.

外部依賴

react 幾乎沒有外部依賴. 通常require()指向的是react自己程式碼庫的一個檔案. 但是也有一些例外.

由於react想要通過庫共享一些諸如Relay的小工具, 所以存在fbjs repository, 而且我們讓他們是同步的. 我們沒有依賴任何node生態系統下的小模組, 因為我們希望facebook的工程師的能能再任何必要的時候修改他們. fbjs中的任何工具都不能被認為是公共api, 並且他們只是為Facebook的一些工程使用, 比如react.

一級目錄

克隆了react的倉庫後你會發現在裡邊有幾個一級目錄.

  • packages目錄包括一些後設資料(如package.json)和react庫提供的所有包的原始碼(src的下面), 如果你想修改程式碼, src下面就是你要花時間最多的地方.

  • fixtures目錄包括了為貢獻者準備的一些小的react的測試應用

  • build是react打包輸出的目錄. 他不在程式碼庫管理範疇, 但是當你第一次打包後就會生成.

文件是放在和react不同的另一個倉庫管理的.

還有一些其他一級目錄, 他們大多是工具層面的, 在你貢獻程式碼時可能不會用到他們能.

共同測試(Colocated Tests)

我們沒有搞個一級目錄來做單元測試. 我們把它放在了被測試檔案相鄰的被稱為__tests__的目錄.

舉個例子, 對於setInnerHTML.js這個檔案的測試被放在與他同級的__tests__/setInnerHTML-test.js這個裡邊.

這個詞不知道怎麼翻譯

Warnings and Invariants

react中使用warning模組顯示警告資訊.

var warning = require(`warning`);

warning(
  2 + 2 === 4,
  `Math is not working today.`
);

當警告條件是false的時候會展示警告資訊

可以這麼理解, 條件應該指示正常的情況, 而不是異常的情況. 就是說第一個引數是true表示的是正常, false是異常.

最好避免使用console取代warnings.

var warning = require(`warning`);

var didWarnAboutMath = false;
if (!didWarnAboutMath) {
  warning(
    2 + 2 === 4,
    `Math is not working today.`
  );
  didWarnAboutMath = true;
}

警告只會在開發模式被開啟. 生產環境下被去掉了. 如果你想阻止某些程式碼塊的執行, 那麼你可以用invariant模組.

var invariant = require(`invariant`);

invariant(
  2 + 2 === 4,
  `You shall not pass!`
);

當條件為false時, 這個方法會直接丟擲異常.

“Invariant” 就是說這個條件為真, 你可以認為他就是做了個斷言.

保持開發環境和生產環境一致是很重要的, 因此invariant在生產環境和開發環境都可以丟擲異常. 生產環境下的錯誤訊息被自動替換成錯誤碼, 以防增加程式碼體積.

Development and Production

你可以使用__DEV__這個為全域性變數指定僅僅在開發環境才執行的程式碼塊.

他是在編譯過程中工作的, 他是在commonjs編譯的時候檢查process.env.NODE_ENV !== `production`這個值.

單獨編譯的時候, 他在未壓縮版是true, 在壓縮版直接被去掉了.

if (__DEV__) {
  // 這裡邊的程式碼只會帶開發環境執行
}

Flow

我們最近開始引入flow做靜態型別檢查, 在檔案頭的註釋裡標註了@flow的使用了型別檢查.

我們接受在現有程式碼加入flow型別檢查的pull request (不錯哎, 可以試著提個pull request哦). Flow的簽名類似下面這樣.

ReactRef.detachRefs = function(
  instance: ReactInstance,
  element: ReactElement | string | number | null | false,
): void {
  // ...
}

時機成熟的時候, 新程式碼要用Flow 簽名, 你可以在本地執行yarn flow用Flow檢查你的程式碼.

動態植入

react在一些模組使用了動態植入. 但是這個東西不太好, 因為他讓程式碼比較難理解了. 他存在的理由是react一開始只把支援dom作為目標的. 但是後來殺出了個React Native, 他是基於react的, 我們不得不加入動態植入好讓react native 過載一些行為.

你可能會看到模組像下面這樣宣告它的動態依賴

// Dynamically injected
var textComponentClass = null;

// Relies on dynamically injected value
function createInstanceForText(text) {
  return new textComponentClass(text);
}

var ReactHostComponent = {
  createInstanceForText,

  // Provides an opportunity for dynamic injection
  injection: {
    injectTextComponentClass: function(componentClass) {
      textComponentClass = componentClass;
    },
  },
};

module.exports = ReactHostComponent;

注入的部分沒有以任何方式特殊處理. 但是規定, 它的意思是這個模組想在執行時有一些依賴(可能是平臺特定的)被注入進去.

程式碼裡邊有幾個注入的入口. 未來, 我們將廢棄掉這種動態植入的機制, 方案是在編譯時以靜態方式處理他們.

多包

react是個monorepo, 他的倉庫包含了多個獨立的包, 因此他們的修改可以合在一起, 而且issues也可以放在一個地方.

React核心

react的核心是所有頂級api, 包括:

  • React.createElement()
  • React.Component
  • React.Children

react核心只包括定義元件必要的api, 並不包括reconciliation演算法和平臺特定程式碼. React DOM和React Native都使用了他們.

react核心的相關程式碼在packages/react裡邊. npm使用時在react這個包裡邊, 瀏覽器版的是react.js, 他掛載一個被稱為React的全域性變數.

Renderers

react起初是為DOM創造的, 但是後臺通過RN被用來支援原生環境了. 這裡介紹加react內部的“renderers”的理念.

“renderers”管理了react樹如何變成平臺可呼叫的東西.

Renderers也在packages裡邊

  • React DOM Renderer 把react 元件渲染進 DOM. 他實現了頂級的ReactDOM APIs, 在react-dom這個npm包裡被暴露出來. 瀏覽器版叫react-dom.js, 通過ReactDOM這個全域性變數暴露出來.

  • React Native Renderer把react元件渲染到原生檢視層裡. 他被RN內部使用.

  • React Test Renderer 把react元件渲染成JSON樹, 他被Jest的一個特性Snapshot Testing使用, 在react-test-renderer這個npm包裡可用.

另一個官方唯一支援的渲染器是react-art, 他曾經是個獨立的庫, 現在被移進來了.

注意

技術上react-native-renderer是很薄的一層, 只是用來和RN的實現相互配合, 真正的平臺相關程式碼是RN庫裡一些native view.

Reconcilers(協調器)

相當多的渲染器, 如Reat DOM, React Native 需要共享一套邏輯. 尤其reconciliation演算法需要足夠的相似, 以便讓rendering, 自定義元件, 狀態, 生命週期函式和refs能跨平臺工作.

為了解決這個問題, 不同的渲染器共用一些程式碼. 我們把React 中的這個部分叫做”reconciler”. 當一個更新比如setState要執行了,Reconcilers就去在元件上呼叫render(), 然後mounts, updates, 或者unmounts他們.

Reconcilers沒有獨立成包, 因為他現在還沒有公共API. 相反, 他僅僅是在渲染器被使用, 比如React DOM , React Native.

Stack Reconciler

Stack Reconciler 是在react15之前實現使用的, 現在已經不用了, 但是下一部分的文件還會有詳細的介紹.

Fiber Reconciler

“Fiber”是為了解決stack reconciler固有問題和修復長期存在的bug所做的努力, 他從react16開始成為預設的Reconciler.

他的主要目標是:

  • 在chunks裡分離可中斷的工作

  • 在過程中重建, 重用work或者改變他的優先順序(瞎翻譯的)的能力

  • 在父子元件前進或回退以只是react中的佈局的能力

  • 在render方法裡返回多個元素的能力

  • 更好的支援錯誤邊際

你可在這裡這裡閱讀更多關於Fiber架構的相關資訊. 但是React16對他做了封裝, 預設不支援非同步特性了.

他的原始碼在packages/react-reconciler裡邊.

事件系統

react實現了一個對renders透明的事件系統, 這個系統被用於react dom 和react native. 原始碼在packages/events;

這裡有個視訊https://www.youtube.com/watch?v=dRo_egw7tBc

相關文章