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

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

内容简介:在组件中调用当然,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() 如何知道该做什么的,我希望这能有所帮助。


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

查看所有标签

猜你喜欢:

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

Learn Python the Hard Way

Learn Python the Hard Way

Zed Shaw / Example Product Manufacturer / 2011

This is a very beginner book for people who want to learn to code. If you can already code then the book will probably drive you insane. It's intended for people who have no coding chops to build up t......一起来看看 《Learn Python the Hard Way》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

RGB CMYK 转换工具
RGB CMYK 转换工具

RGB CMYK 互转工具