[譯]react的setState如何知道該作什麼 --Dan Abramov

原文: 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-domreact-dom / serverreact-nativereact-test-rendererreact-art是在renderers中的一些例子(你也能夠創建屬於你本身的)。

因此,不管在什麼平臺,react包均可以正常工做。他對外暴露的全部的內容,例如: React.ComponentReact.createElementReact.Children的做用和Hook,都獨立於目標平臺。不管運行React DOMReact 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-domreact-native等)提供了React功能和特定於平臺的邏輯的實現。其中一些代碼是共享的(「reconciler」),但這是各個渲染器的實現細節。


如今咱們知道了爲何每次有新的功能都會同時更新reactreact-dom包。例如,當React 16.3添加了Context API時,React.createContext()在React包上對外暴露。

可是React.createContext實際上並無實現上下文功能。例如,React DOMReact 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將是一個沒法解析ProviderConsumer類型的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 DOMReact DOM ServerReact 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作了一樣的事情

相對於setStateupdater字段而言,Hooks使用dispatcher對象。 當調用React.useStateReact.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 DOMReact 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.setStateuseState如何知道該怎麼作,我但願本文會對你有所幫助。

相關文章
相關標籤/搜索