世界上最伟大的投资就是投资自己的教育

首页React
xiaohesong · 凡人

react 的 setState 如何知道该做什么 --Dan Abramov

xiaohesong发布于16340 次阅读

原文: How Does setState Know What to Do?

原译文: react 的 setState 如何知道他要做什么 -- 可查看此系列的其他文章

译:可能看到标题的时候会想,怎么去做还不是看代码吗?react 中的setState不就是负责更新状态码?于是就抱着好奇心看下去了。

当你在组件中调用setState的时候,你认为让发生了什么?

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在处理?

更新DOM听起来似乎是React DOM在负责处理。但是我们调用的是this.setState,这个api是来自react,并非是React DOM。并且我们的React.Component是定义在React里的。

所以React.Component.prototype.setState()是如何去更新DOM的。

事先声明: 就像本博客大多数其他文章一样,你其实可以完全不用知道这些内容,一样可以很好的使用react。这系列的文章是针对于那些好奇react内部原理的一些人。所以读不读,完全取决于你。


我们可能会认为React.Component里包含了DOM的一些更新逻辑。

但是如果是我们猜想的这样,那么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如何知道该怎么做,我希望本文会对你有所帮助。

本站文章均为原创内容,如需转载请注明出处,谢谢。

0 条回复
暂无回复~~
喜欢
统计信息
    学员: 29003
    视频数量: 1973
    文章数量: 489

© 汕尾市求知科技有限公司 | Rails365 Gitlab | Qiuzhi99 Gitlab | 知乎 | b 站 | 搜索

粤公网安备 44152102000088号粤公网安备 44152102000088号 | 粤ICP备19038915号

Top