1 引言
setState
是 React 框架最常用的命令,它是用來更新狀態的,這也是 React 框架劃時代的功能。
但是 setState
函式是 react
包匯出的,他們又是如何與 react-dom
react-native
react-art
這些包結合的呢?
通過 how-does-setstate-know-what-to-do 這篇文章,可以解開這個祕密。
2 概述
setState
函式是在 React.Component
元件中呼叫的,所以最自然的聯想是,更新 DOM 的邏輯在 react
包中實現。
但是 react
卻可以和 react-dom
react-native
react-art
這些包打配合,甚至與 react-dom/server
配合在服務端執行,那可以肯定 react
包中不含有 DOM 更新邏輯。
所以可以推斷,平臺相關的 UI 更新邏輯分佈在平臺相關的包裡,react
包只做了代理。
React 引擎不在 react 包裡
從 react 0.14 版本之後,引擎程式碼就從 react 包中抽離了,react 包僅僅做通用介面抽象。
也就是說,react 包定義了標準的狀態驅動模型的 API,而 react-dom
react-native
react-art
這些包是在各自平臺的具體實現。
各平臺具體的渲染引擎實現被稱為 reconciler,通過這個連結可以看到 react-dom
react-native
react-art
這三個包的 reconciler 實現。
這說明了 react 包僅告訴你 React 擁有哪些語法,而並不關心如何實現他們,所以我們需要結合 react 包與 react-xxx 一起使用。
對於 context
,react 包僅僅會做如下定義:
// A bit simplified
function createContext(defaultValue) {
let context = {
_currentValue: defaultValue,
Provider: null,
Consumer: null
};
context.Provider = {
$$typeof: Symbol.for("react.provider"),
_context: context
};
context.Consumer = {
$$typeof: Symbol.for("react.context"),
_context: context
};
return context;
}
具體用到時,由 react-dom
和 react-native
決定用何種方式實現 MyContext.Provider
這個 API。
這也說明了,如果你不同步升級 react
與 react-dom
版本的話,就可能碰到這樣的報錯:fail saying these types are invalid,原因是 API 定義與實現不匹配。
setState 怎麼呼叫平臺實現
每個平臺對 UI 更新邏輯的實現,會封裝在 updater
函式裡,所以不同平臺程式碼會為元件新增各自的 updater
實現:
// Inside React DOM
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater;
// Inside React DOM Server
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater;
// Inside React Native
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;
不同於 props
, updater
無法被直接呼叫,因為這個 API 是由 react 引擎在 setState
時呼叫的:
// A bit simplified
setState(partialState, callback) {
// Use the `updater` field to talk back to the renderer!
this.updater.enqueueSetState(this, partialState, callback);
}
關係可以這麼描述:react
-> setState -> updater <- react-dom
等。
Hooks
Hooks 的原理與 setState
類似,當呼叫 useState
或 useEffect
時,其內部呼叫如下:
// In React (simplified a bit)
const React = {
// Real property is hidden a bit deeper, see if you can find it!
__currentDispatcher: null,
useState(initialState) {
return React.__currentDispatcher.useState(initialState);
},
useEffect(initialState) {
return React.__currentDispatcher.useEffect(initialState);
}
// ...
};
ReactDOM 提供了 __currentDispatcher
(簡化的說法):
// In React DOM
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;
let result;
try {
result = YourComponent(props);
} finally {
// Restore it back
React.__currentDispatcher = prevDispatcher;
}
可以看到,Hooks 的原理與 setState
基本一致,但需要注意 react
與 react-dom
之間傳遞了 dispatch
,雖然你看不到。但這個 dispatch
必須對應到唯一的 React 例項,這就是為什麼 Hooks 不允許同時載入多個 React 例項的原因。
和 updater
一樣,dispatch
也可以被各平臺實現重寫,比如 react-debug-hooks
就重寫了 dispatcher。
由於需要同時實現 readContext
, useCallback
, useContext
, useEffect
, useImperativeMethods
, useLayoutEffect
, useMemo
, useReducer
, useRef
, useState
,工程量比較浩大,建議瞭解基本架構就足夠了,除非你要深入參與 React 生態建設。
3 精讀
與其他 React 分析文章不同,本文並沒有過於刨根問題的上來就剖析 reconciler 實現,而是問了一個最基本的疑問:為什麼 setState
來自 react
包,但實現卻在 react-dom
裡?React 是如何實現這個 magic 的?
通過這個疑問,我們瞭解了 React 更上層的抽象能力,如何用一個包制定規範,用 N 包去實現它。
介面的力量
在日常程式設計中,介面也擁有的強大力量,下面舉幾個例子。
UI 元件跨三端的介面
由於 RN、Weex、Flux 的某些不足,越來越多的人選擇 “一個思想三端實現” 的方式做跨三端的 UI 元件,這樣既兼顧了效能,又可以照顧到平臺差異性,對不同平臺元件細節做定製優化。
要實施這個方案,最大問題就是介面約定。一定要保證三套實現遵循同一套 API 介面,業務程式碼才可以實現 “針對任意一個平臺編寫,自動移植到其他平臺”。
比較常用的做法是,通過一套統一的 API 檔案約束,固定元件的輸入輸出,不同平臺的元件做平臺具體實現。這個思想和 React 如出一轍。
當然 RN 這些框架本身也是同一介面在不同平臺實現的典型,只是做的不夠徹底,JS 與 Native 的通訊導致了效能不如源生。
通用資料查詢服務
通用資料查詢服務也比較流行,通過磨平各資料庫語法,讓使用者通過一套 SQL 查詢各種型別資料庫的資料。
這個方案中,一套通用的查詢語法就類似 React 定義的 API,執行階段會轉化為各資料庫平臺的 SQL 方言。
小程式融合方案
現在這種方案很火。通過基於 template 或者 jsx 的語法,一鍵釋出到各平臺小程式應用。
這種方案一定會抽象一套通用語法,甚至幾乎等價與 react
與 react-dom
的關係:所有符合規範的語法,轉化為各小程式平臺的實現。
4 總結
這種分平臺實現方案與跨平臺方案還是有很大區別的,像 JAVA 虛擬機器本質還是一套實現方案。而分平臺的實現可以帶來最原生的效能與體驗,同樣收到的約束也最大,應該其 API 應該是所有平臺支援的一個子集。
另外,這種方案不僅可以用於 一套規範,不同平臺的實現,甚至可以用在 “同一平臺的實現”。
無論是公司還是開源節界,都有許多重複的輪子或者平臺,如果通過技術委員會約定一套平臺的實現規範,大家都遵循這個規範開發平臺,那未來就比較好做收斂,或者說收斂的第一步都是先統一 API 規範。
留下一個思考題:還有沒有利用 setState
規範與實現分離的思想案例?歡迎留下你的答案。
如果你想參與討論,請點選這裡,每週都有新的主題,週末或週一釋出。前端精讀 – 幫你篩選靠譜的內容。