setState是如何知道该怎么做的?

栏目: IOS · Android · 发布时间: 6年前

内容简介:要有心里准备,这篇文章抽象又拗口,希望有人可以将它视觉化!当你在组件里调用很明显,React会随着新的

要有心里准备,这篇文章抽象又拗口,希望有人可以将它视觉化!

当你在组件里调用 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} 状态重渲染组件(component),更新DOM,匹配返回 <h1>Thanks</h1> 元素(element)。

似乎很简单。不过问题来了,是 React 干的还是 React DOM 干的?

更新DOM听起来像 React DOM 负责的,但我们调用 this.setState() ,和 React DOM 似乎没有关联, React.Component 这个基类是在React中声明的。

那么 React.Component 中的 setState() 是如何更新DOM的?

免责声明:与多数 其他 文章 一样,这篇文章,对React实际使用来说不是必须的,它适合喜欢追寻万物原理的朋友们,谨慎选择 !

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

但是如果是这样的话, this.setState() 如何在其他环境奏效?例如,React Native 的组件也扩展了 React.Component ,它们就像前面那样调用 this.setState() ,且 React Native 使用在Android和iOS原生视图而不是DOM。

你可能也会对 React的 Test Renderer 或 Shallow Renderer 有些印象,这两种测试方案都可以渲染普通组件并在其中调用 this.setState() ,但它们和DOM都没关系。

如果你用过像 React ART 这样的渲染器(renderer),你可能也知道页面有可能使用多个渲染器(例如,ART组件运行于React DOM树中),这使得全局标志或变量不再可靠。

所以,针对不同平台代码, React.Component 以某种委托方式处理state更新 。在我们弄清楚怎么回事前,先深入探讨下如何及为什么要分离包(packages)。

有一种常见的误解,即React的“引擎”在 react 依赖包中,这不是真的。

实际上,自从React 0.14拆分依赖包以来, react 依赖包特意地只暴露 定义 组件(components)的APIs,React绝大多数 实现 都放在 “渲染器”,

react-domreact-dom/serverreact-nativereact-test-rendererreact-art 都是渲染器样例(你可以 搭建自己的 )。

这也是为什么 react 依赖包不管面向哪个平台都可行,它所有的导出,例如 React.ComponentReact.createElementReact.Children 和最近的Hooks,都独立于目标平台,无论你运行 React DOM、React DOM Server或者React Native,你都可以用同一种方式导入使用组件。

相比之下,渲染器依赖包暴露特定平台的APIs,如 ReactDOM.render() ,可以将React组件插入DOM节点中。每个渲染器都会提供一个类似的API,理想情况下,大多数 组件 不需要从渲染器导入任何内容,这使它们更灵活。

大多数人认为React的“引擎”在每个渲染器中。不过许多渲染器确实包含了同一份副本代码 —— 我们称为 "reconciler" 。有个构建步骤将 reconciler 代码与渲染器代码融合成一份高度优化过的代码,以获得更好的性能。(复制代码通常不利于依赖包大小,但绝大多数用户一次只需要一个渲染器,例如 react-dom )

这里要说的是, react 依赖包只让你知道React有哪些功能,但不知道功能是如何实现的。渲染器依赖包( react-domreact-native 等)提供了React功能的实现和平台特性的逻辑。其中一些代码是共享的("reconciler"),但更多的是各个渲染器的具体实现。

现在我们知道为什么有功能时, reactreact-dom 依赖包需要同时更新了,比如说,在React 16.3添加 Context API 时,React依赖包会暴露 React.createContext()

React.createContext() 实际上并没有 实现 context功能,React DOM 与 React DOM Server 的实现是不同的。例如, createContext 返回一些 plain objects:

// 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> 时, 渲染器 决定如何处理它们。React DOM可能以一种方式跟踪context,而React DOM Server可能会采用另一种方式。

如果你更新 react 到16.3+而没更新 react-dom ,你将使用的渲染器便不知道什么是 ProviderConsumer 这也是旧的 react-dom 会引发类型无效错误的原因

React Native同样有这警告。不过不同于 React DOM,一次React更新发布不会“迫使”React Native也立即发布新版本,它有自己一套发行时间表。更新的渲染器代码将 单独同步 到React Native代码库中。所以React Native和React DOM同一个功能,可以用上的时间是不同的。

好了,我们现在知道 react 依赖包不包含任何有趣的内容,因为具体实现放到 react-domreact-native 等渲染器中了。但是这没能解决我们的问题, React.Component 中的 setState() 是如何与对应的渲染器“交流的”。

答案是每个渲染器在创建的class上设置一个特殊字段。这个字段叫做 updater 。这不是由你设置的,而是React DOM、React DOM Server、React Native在你实例class后给你加上的:

// 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.Component 中的 setState 实现 ,它所做的就是将任务全部委托给实例此组件的渲染器:

// 简化后的代码
setState(partialState, callback) {
  // 用`updater` 反馈给渲染器
  this.updater.enqueueSetState(this, partialState, callback);
}
复制代码

React DOM Server 也许打算 忽略state更新并警告你,而React DOM和React Native会用复制来的"reconciler"去 处理它

这也是为什么即使 this.setState() 定义在React依赖包中,依然可以更新DOM。它会获取由React DOM设置的 this.updater ,并让React DOM调度和处理更新。

我们现在知道class了,那Hooks是怎么做的?

当大家第一次看到Hooks API,很可能会想: useState 怎么“知道该怎么做”?猜想是它的 this.setState() 比基于 React.Component 的更“神奇”。

但正如我们今天看到的,基于class的 setState() 实现一直是一种错觉,除了调用指向当前的渲染器之外,它不参与任何操作。 useState Hook 也同样如此

Hooks使用 dispatcher 对象而不是 updater 字段 。在你调用 React.useState()React.useEffect() 、或者其他内置Hook时,这些都会转发给当前的dispatcher。

// In React (简化)
const React = {
  // 真正的属性隐藏得有点深,你可以尝试去找找看!
  __currentDispatcher: null,

  useState(initialState) {
    return React.__currentDispatcher.useState(initialState);
  },

  useEffect(initialState) {
    return React.__currentDispatcher.useEffect(initialState);
  },
  // ...
};
复制代码

而每种渲染器在组件渲染之前会设置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 这样的渲染器需要获取同一个 react 依赖包的原因,否则,你的组件不会“看到”这个dispatcher!如果在同一棵组件树中存在 多个React副本 ,就有可能发生问题。不过这样容易出现隐蔽bug,所以Hooks会强迫你在发生前就解决依赖包重复问题。

虽然我们不鼓励这样做,但为了更适用于某些情景,你可以在技术上自行覆盖dispatcher( __currentDispatcher 是我编造的,不过你可以在代码库中找到真实的名称),例如,React DevTools会用 一个专门定制的dispatcher 通过捕获JavaScript堆栈轨迹来描绘反馈Hooks树。 不要在家重复这样做了

这也意味着Hooks本身并不依赖于React。如果将来有更多的类库想复用React里的Hooks理念,理论上dispatcher可以挪过去用并且作为一个更少“可怕”名称的一流API展现出来。在开发过程中,我们应该避免过早抽象概念,直到我们不得不这么做了。

updater 字段和 __currentDispatcher 对象都形成于一个叫 依赖注入 的通用编程原理。这两种情况里,渲染器将诸如 setState 之类的功能实现“注入”到通用的React依赖包中,组件因此以声明为主。

在使用React时,你不需要思考这些是怎么跑起来的。我们希望React开发者花更多的时间在应用程序代码上,而不是像依赖注入这些抽象概念上。但如果你想知道 this.setState() 或者 useState 是如何知道怎么做的,我希望这会有所帮助。

翻译原文 How Does setState Know What to Do? (2018-12-09)


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

群体智能

群体智能

James Kennedy、Russell C Eberhart、Yuhui Shi / 人民邮电出版社 / 2009-2-1 / 75.00元

群体智能是近年来发展迅速的人工智能学科领域.通过研究分散,自组织的动物群体和人类社会的智能行为, 学者们提出了许多迥异于传统思路的智能算法, 很好地解决了不少原来非常棘手的复杂工程问题.与蚁群算法齐名的粒子群优化(particle swarm optimization, 简称PSO)算法就是其中最受瞩目,应用最为广泛的成果之一. 本书由粒子群优化算法之父撰写,是该领域毋庸置疑的经典著作.作者......一起来看看 《群体智能》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具