[译]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如何知道该怎么作,我但愿本文会对你有所帮助。

相关文章
相关标签/搜索