【译】setState 是如何知道该做什么的?

栏目: 服务器 · 发布时间: 5年前

内容简介:在组件中调用当然,React重新渲染状态为看起来很直接,但是等等,这是React的功劳还是React DOM呢?
原文链接:https://overreacted.io/how-does-setstate-know-what-to-do/ by Dan Abramov
复制代码

在组件中调用 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 } 并且会返回 <h1>Thanks</h1> 元素以更新 DOM

看起来很直接,但是等等,这是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 一样的渲染器,你可能也知道在页面中使用多个渲染器是可能的。(例如,ART组件在React DOM树的内部工作。)这使得全局标志或者变量站不住脚。

因此在某种程度上 React.Component 将状态更新的处理委托给特定平台的程序处理。在我们了解这是怎样发生的之前,让我们深入到包是如何分离的以及其中的原因去。

有一个常见的误解,认为React“引擎”驻留在React包中。这不是真的。

事实上,自从包在React 0.14中分裂后, react 包试图只暴露用于定义组件的APIs。大多数React的实现都存在于“renderers”中。

react-dom , react-dom/server , react-native , react-test-renderer , react-art 是一些渲染器的例子(你也可以 创建你自己的

这就是 react 包不管针对哪个平台都有用的原因。所有导出,例如 React.Component , React.createElement , React.Children 实例与(最终的)Hooks,都独立于目标平台。无论你运行 React DOMReact DOM Server 还是 React Native ,组件都将以相同的方式导入和使用它们。

形成鲜明比较的是, renderer 包公开了特定于平台的 api ,比如 ReactDOM.render() ,它允许你将一个React层次结构挂载到DOM节点中。每一个 renderer 都提供一个类似的API。理想状态下,大多数组件没必要从 renderer 中导入任何东西。这使它们更轻便。

大多数人想象每个 renderer 中都存在React“engine”。许多 renderers 都包含相同代码的副本——我们称其为 “reconciler” 。构建步骤将协调(reconciler)程序代码与渲染器(renderer)程序代码合并到一个高度优化的包中,以获得更好的性能。(复制代码通常对于包尺寸来说不是很好,但是大多数React用户一次只需要一个渲染器,例如 react-dom .)

这里的要点是react包只允许你使用react特性,但是不知道它们是如何实现的。 renderer 程序包( react-dom , react-native 等等)提供了React特性和特定平台的逻辑的实现。其中一些代码是共享的(“reconciler”),但这是单个 renderers 程序的实现细节。

现在我们知道了为什么需要为新特性更新 reactreact-dom 包。例如,当React 16.3中添加了 Context API 后, React.createContext() 就在React包中被暴露出来。

但是 React.createContext() 没有真正实现context特性。例如,在 React DOMReact DOM Server 之间,实现的需求有所不同。所以 createContext() 仅返回一些普通对象:

// 简化版
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.Comsumer> 时,是 renderer 决定如何处理它们。 React DOM 可能以一种方式跟踪 context 的值,但 React DOM Server 可能采用不同的方式。

因此,如果你更新 react 到16.3+,但不更新 react-dom ,那么你将使用一个没有意识到特定的 ProviderConsumer 类型的 renderer 。这就是为什么旧的 react-dom 无法说这些类型是失效的

同样的警告可以运用到 React Native 中。然而,不像 React DOMReact 的版本发布不会立即“强制” React Native 的版本发布。它们有独立的版本发布表。更新后的 renderer 程序代码每隔几周就会被单独同步到 React 本地存储库中一次。这就是为什么 React Native 中的特性与 React DOM 中的特性在时间表上有所不同的原因。

好了,现在我们知道了 react 包没有包含任何有趣的东西,并且它的实现都存在与 renderers 中像 react-dom , react-native 等等。但是这并没有回答我们的问题。 React.Component 中的 setState() 是如何和正确的 renderer “交谈”的?

**答案是每个 renderer 在创建的类上设置一个特殊的字段。**这个特殊字段被称作 updater 。这不是你要设置的——更确切的说,它是在创建类的实例之后由 React DOMReact DOM Server 或者 React Native 设置的:

// React DOM 内部
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMUpdater;

// React DOM Server 内部
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactDOMServerUpdater;

// React Native 内部
const inst = new YourComponent();
inst.props = props;
inst.updater = ReactNativeUpdater;
复制代码

看一下 setStateReact.Component 中的实现 ,它所做的只是将工作委托给创建这个组件实例的 renderer :

// 简化版
setState(partialState, callback) {
    // 使用`updater`与渲染器对话
    this.updater.enqueueSetState(this, partialState, callback);
}
复制代码

React DOM Server 可能想要 忽略状态更新并且警告你,而React DOM和React Native会让它们的协调器副本来 处理它

这就是this.setstate()如何更新DOM的,即使它是在React包中定义的。但它读取的 React DOM 设置的 this.updater ,并且让 React DOM 进行调度和处理更新。

我们现在知道类(Class)了,但是钩子(Hooks)呢?

当人们第一次看到Hooks API的提议时,他们经常会想: useState 是如何知道要做什么的?以为它比基于基础 React.Component 类的 this.setState() 更加神奇。

但是就像我们今天所看到的,基础类的 setState() 实现一直都是幻觉。它没有做任何事情除了将调用转到当前的 renderer 中。 useState 也做了 同样的事情

取代了 updater 字段,Hooks使用了“dispatcher”对象。当你调用 React.useState() , React.useEffect() ,或者其它被创建的钩子时,这些调用会转向到当前的 dispatcher 中。

// 在React中(简化版)
const React = {
    // 真正的属性被藏得有点深,如果你能找到它的话就看一下
    _currentDispatcher: null,
    
    useState(initialState) {
        return React.__currentDispatcher.useState(initialState);
    },
    
    useEffect(initialState) {
        return React.__currentDispatcher.useEffect(initialState);
    },
    
    // ... 
}
复制代码

特定的 renderer 在你的组件渲染之前就设置了dispatcher:

// React DOM 中
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;

let result;
try {
    result = YourComponent(props);
} finally {
    // 恢复过来
    React.__currentDispatcher = prevDispatcher;
}

复制代码

例如,React DOM Server 这里 的实现,和 这里 的被React DOM和React Native共享的调节器的实现。

这就是为什么像 react-dom 这样的 renderer 程序需要访问调用钩子的同一个react包。否则,你的组件不会“看到”dispatcher!当你在同一个组件树中有 多个React副本 时,这可能无法工作。然而,这总是会导致一些模糊的bug,因此 Hooks 迫使你在付出代价之前解决包复制问题。

虽然我们不鼓励这样做,但是对于高级 工具 用例,你可以重写 dispatcher 。(我在 __currentDispatcher 名称上撒谎了,但是你可以在 React repo 中找到真正的名称。)例如, React DevTools 将使用 一个特殊的专门构建的dispatcher 通过捕获 JavaScript 堆栈跟踪来反映Hooks树。 别在家里重复做这件事。

这也意味着钩子本身并不与 react 绑定。如果将来有更多的库希望重用相同的原语钩子,理论上, dispatcher 可以转移到一个单独的包中,并作为一个一流的没有那么可怕的名称的API公开。在实践中,我们希望在需要抽象之前避免过早地抽象。

updater 字段和 __currentDispatcher 对象都是 依赖注入 的通用编程原则的形式。在这两种情况下, renderer 程序都将 setState 等特性的实现“注入”到通用的React包中,以使组件更具声明性。

当你使用React时你不需要考虑这是怎样工作的。我们希望用户花更多的时间思考他们的应用程序代码,而不是像 依赖注入 这样的抽象概念。但是如果你曾经想了解 this.setstate()useState() 如何知道该做什么的,我希望这能有所帮助。


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

查看所有标签

猜你喜欢:

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

Haskell函数式编程基础

Haskell函数式编程基础

Simon Thompson / 科学出版社 / 2013-7-1 / 129.00

《Haskell函数式编程基础(第3版)》是一本非常优秀的Haskell函数式程序设计的入门书,各章依次介绍函数式程序设计的基本概念、编译器和解释器、函数的各种定义方式、简单程序的构造、多态和高阶函数、诸如数组和列表的结构化数据、列表上的原始递归和推理、输入输出的控制处理、类型分类与检测方法、代数数据类型、抽象数据类型、惰性计算等内容。书中包含大量的实例和习题,注重程序测试、程序证明和问题求解,易......一起来看看 《Haskell函数式编程基础》 这本书的介绍吧!

HTML 编码/解码
HTML 编码/解码

HTML 编码/解码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试

RGB HSV 转换
RGB HSV 转换

RGB HSV 互转工具