原文: How Does setState Know What to Do?html
原譯文: react的setState如何知道他要作什麼react
譯:可能看到標題的時候會想,怎麼去作還不是看代碼嗎?react中的
setState
不就是負責更新狀態碼?因而就抱着好奇心看下去了。git
當你在組件中調用setState
的時候,你認爲讓發生了什麼?github
import React from 'react';
import ReactDOM from 'react-dom';
class Button extends React.Component {
constructor(props) {
super(props);
this.state = { clicked: false };
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
this.setState({ clicked: true });
}
render() {
if (this.state.clicked) {
return <h1>Thanks</h1>;
}
return (
<button onClick={this.handleClick}> Click me! </button>
);
}
}
ReactDOM.render(<Button />, document.getElementById('container')); 複製代碼
固然,react
會在下一個{clicked: true}
狀態的時候re-render
組件而且更新DOM
去返回<h1>Thanks</h1>
元素。編程
看起來很簡單,對吧?等一下,請考慮下,這個是react
在處理?或者是ReactDOM
在處理?react-native
更新DOM
聽起來彷佛是React DOM
在負責處理。可是咱們調用的是this.setState
,這個api
是來自react
,並不是是React DOM
。而且咱們的React.Component
是定義在React
裏的。api
因此React.Component.prototype.setState()
是如何去更新DOM
的。數組
事先聲明: 就像本博客大多數的其餘的文章同樣,你其實能夠徹底不用知道這些內容,同樣能夠很好的使用react
。這系列的文章是針對於那些好奇react
內部原理的一些人。因此讀不讀,徹底取決於你。dom
咱們可能會認爲React.Component
裏包含了DOM
的一些更新邏輯。ide
可是若是是咱們猜測的這樣,那麼this.setState()
如何在其餘環境中正常工做?好比在React Native中的組件也是繼承於React.Component
, React Native應用像上面同樣調用this.setState()
,但React Native可使用Android和iOS原生的視圖而不是DOM。
再好比,你可能也熟悉React Test Renderer或Shallow Renderer。這些均可以讓你渲染普通組件並在其中調用this.setState()
。可是他們都不適用於DOM
。
若是你使用過像React ART這樣的渲染器,你便會知道能夠在頁面上使用多個渲染器。(例如,ART 組件在React DOM
中工做)。這使得全局標誌或變量沒法工做。
因此**React.Component
以某種方式委託處理狀態更新到特定的平臺。** 在咱們理解這是如何發生以前,讓咱們深刻了解包的分離方式和緣由。
有一種常見的誤區就是React
「引擎(engine)」存在React
包中。這實際上是不對的。
事實上,自從React 0.14拆分包以來,react包只是暴露了用於定義組件的API。React的大多數實現都在「渲染器(renderers)」中。
react-dom
,react-dom / server
,react-native
,react-test-renderer
,react-art
是在renderers
中的一些例子(你也能夠創建屬於你本身的)。
因此,不管在什麼平臺,react
包均可以正常工做。他對外暴露的全部的內容,例如: React.Component
,React.createElement
,React.Children
的做用和Hook,都獨立於目標平臺。不管運行React DOM
,React DOM Server
仍是React Native
,組件都將以相同的方式導入和使用它們。
相比之下,renderer
包對外暴露了特定於平臺的api
,像ReactDOM.render
就可讓你把React
的層次結構掛載到DOM
節點。每一個renderer
都提供了像這樣的API
。理想狀況下,大多數組件不須要從renderer
導入任何內容。這使它們更輕便易用。
大多數人都認爲react的"引擎(engine)"在每一個renderer
中。 許多renderer
都包含相同代碼的副本 - 咱們將其稱爲「reconciler(和解)」。構建步驟將reconciler(和解)的代碼與renderer(渲染器)代碼一塊兒成爲一個高度優化的捆綁包,以得到更好的性能。(複製代碼對於包大小一般不是很好,但絕大多數React用戶一次只須要一個渲染器,例如react-dom。)
這裏要說的是,react包只容許你使用React功能,但不知道它們是如何實現的。renderer
包(react-dom
,react-native
等)提供了React功能和特定於平臺的邏輯的實現。其中一些代碼是共享的(「reconciler」),但這是各個渲染器的實現細節。
如今咱們知道了爲何每次有新的功能都會同時更新react
和react-dom
包。例如,當React 16.3
添加了Context API
時,React.createContext()
在React包上對外暴露。
可是React.createContext
實際上並無實現上下文功能。例如,React DOM
和React DOM Server
之間的實現須要有所不一樣。因此createContext()
返回一些普通對象:
// 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;
}
複製代碼
當你在代碼中使用<MyContext.Provider>
或者 <MyContext.Consumer>
時,這就由renderer
去決定如何處理他們。React DOM
可能以一種方式跟蹤上下文值,但React DOM Server
可能會採用不一樣的方式。
因此若是你更新react
到16.3+,可是沒有更新react dom
, 那麼你使用的這個renderer
將是一個沒法解析Provider
和Consumer
類型的renderer
。 這就是爲何舊的react-dom
會失敗報錯這些類型無效。
一樣的警告也適用於React Native。可是,與React DOM不一樣,React的版本更新不會迫使React Native的版本去當即更新。他有一個本身的發佈週期。幾周後,更新過的renderer
會單獨同步到React Native庫中。這就是爲何React Native和React DOM可用功能的時間不一致的區別
好吧,如今咱們知道了React包中不包含咱們感興趣的內容,並且這些實現是存在於像react-dom
, react-native
這樣的renderer
中。可是這些並不能回答咱們的問題 -- React.Component
中的setState
是如何知道他要幹什麼的(與對應的renderer
協同工做)。
答案是在每一個建立renderer
的類上設置一個特殊的字段。 這個字段就叫作updater
。這不是你想要設置啥就設置啥,你不能夠設置他,而是要在類的實例被建立後再去設置React DOM
,React DOM Server
或React Native
:
// 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;
複製代碼
React DOM Server 可能想要忽略狀態的更新而且給你一個警告,然而React DOM和React Native會拷貝一份reconciler
(和解)代碼去處理他
這就是爲何this.setState
定義在React
的包中仍然能夠更新DOM
的緣由。他經過讀取this.updater
去獲取,若是是React DOM
, 就讓React DOM
調度並處理更新。
咱們如今知道了類的操做方式,那麼hooks
呢?
當大多數的人看到Hooks
提案的API
時,他們經常想知道: useState
是怎麼'知道該去作什麼’?假設他會比this.setState
更加神奇。
可是正如咱們如今所看到的這個樣子,對於理解setState
的實現一直是一種錯覺。他除了會將調用做用到對應的renderer
以外不會再作其餘任何的操做。實際上useState
這個Hook
作了一樣的事情。
相對於setState
的updater
字段而言,Hooks
使用dispatcher
對象。 當調用React.useState
,React.useEffect
或其餘內置的Hook
時,這些調用將轉發到當前調度程序(dispatcher
)。
// 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);
},
// ...
};
複製代碼
而且在渲染你的組件以前,各個renderer
會設置dispatcher
。
// In React DOM
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;
let result;
try {
result = YourComponent(props);
} finally {
// Restore it back
React.__currentDispatcher = prevDispatcher;
}
複製代碼
例如,React DOM Server
實如今這裏,React DOM
和React Native
共享的reconciler
實現就在這裏。
這就是爲何像react-dom
這樣的renderer
須要訪問你調用Hooks
的同一個React包的緣由。不然,你的組件將不會知道dispatcher
!當在同一組件樹中有多個React副本時,這可能不會如期工做。可是,這會致使一些那難以理解的錯誤,因此Hook
會迫使解決包重複問題。
雖然咱們不鼓勵你這樣作,可是對於高級工具用例,你能夠在此技術上重寫dispatcher
。(我對__currentDispatcher
這名稱撒了謊,這個不是真正的名字,但你能夠在React
庫中找到真正的名字。)例如,React DevTools將使用特殊的專用dispatcher程序經過捕獲JavaScript堆棧跟蹤來反思Hooks
樹。不要本身在家裏重複這個。
這也意味着Hooks
自己並不依賴於React。若是未來有更多的庫想要重用這些原始的Hook
,理論上dispatcher
能夠移動到一個單獨的包中,並做爲一個普通名稱的API對外暴露。在實踐中,咱們寧願避免過早抽象,直到須要它的時候再說。
updater
字段和__currentDispatcher
對象都是一種稱爲依賴注入的編程原則的形式。在這些狀況下,renderer
注入一些好比setState
這樣的功能到React的包中,這樣來保持組件更具備聲明性。
在使用react
的時候,你不須要去考慮他的工做原理。咱們但願React用戶花更多時間考慮他們的代碼而不是像依賴注入這樣的抽象概念。可是若是你想知道this.setState
或useState
如何知道該怎麼作,我但願本文會對你有所幫助。