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