原文链接:How Does setState Know What to Do?

    当我们在一个组件里面调用 setState 方法的时候,觉得到底发生了什么?

    1. import React from 'react';
    2. import ReactDOM from 'react-dom';
    3. class Button extends React.Component {
    4. constructor(props) {
    5. super(props);
    6. this.state = { clicked: false };
    7. this.handleClick = this.handleClick.bind(this);
    8. }
    9. handleClick() {
    10. this.setState({ clicked: true });
    11. }
    12. render() {
    13. if (this.state.clicked) {
    14. return <h1>Thanks</h1>;
    15. }
    16. return (
    17. <button onClick={this.handleClick}>
    18. Click me!
    19. </button>
    20. );
    21. }
    22. }
    23. ReactDOM.render(<Button />, document.getElementById('container'));

    当然,当点击按钮的时候React会以 { clicked: true } 这个状态为准并更新DOM元素,以匹配

    Thanks
    这个返回元素。
    似乎非常的直截了当,但是等等,是 React 做的吗?还是 React Dom 做到的?
    更新DOM听起来像是 React DOM 负责做的事。但是我们调用却是 this.setState() 方法,而不是React DOM中的任何方法。并且 React.Component 这个基础类是定义在 React 中的。
    所以我们想知道 到底 setState() 方法是如何更新 DOM的?

    声明:即使不知道本文说的内容也可以高效的使用React。如果你想要刨根问底,那么继续吧。

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

    但是如果按照这个逻辑的话,this.setState() 是如何在其他环境工作的?比如,在React Native的app中,组件也是继承React.Component。并且也是调用this.setState() 方法就如上述所言,然而React Native操纵的是Android或者IOS的界面而非DOM。

    如果你用过一些渲染器,比如 React Art,那么你应该知道在同一个页面上是有可能存在多与一个的渲染器。(比如,React Art是在DOM树内部工作。)这使得认为是全局标识或者变量来控制的观点是站不住脚的。

    所以React.Component以某种形式代理了具体平台相关状态处理代码逻辑。在我们深入理解之前,先来了解下React是如何分割模块(包)以及其原因所在。


    通常我们会有一个误解,以为 React‘引擎’是在 React模块(包)下面的。这个其实是错误的认知。

    事实上,自React 0.14版本以来,React 模块(包)有意的只暴露了定义逐渐的API。React的大多数真正的实现全都在‘渲染器’中。

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

    这也是为什么React模块(包)在任何平台都有用的原因。对于所有暴露的API,比如React.Componet, React.createElement, React.Children等通用方法类和Hooks, 都是独立于目标平台的。不管你是使用 ReactDOM, React DOM Server 或者是 React Native,组件都是同样的方式引入和使用的。

    不同的是,各个渲染器的模块回暴露平台相关的API,比如 ReactDOM.render() 可以把一个React构建组件转换为DOM节点。每一个渲染器都会提供如此的API。理想情况,任何组件都不引用任何渲染器的方法,这样可以组件更加的可移植性。

    大多数人认为的React引擎是存在于每个独立的渲染器内的。许多的渲染器有包含了一段相似的代码 — 我们称之为 “ reconciler ” 。为了更好的性能,会通过一个构建过程来把 reconciler 代码和渲染器代码融合在单个个高度优化的打包文件内。(通常而言,复制代码无助于打包后文件大小的缩减,但是绝大多数React用户只使用一个渲染器,比如React DOM)

    React模块只是让我们能够方便的使用React的特性,但是它并不负责实现。这些渲染器模块(包)(比如,react-native, react-dom等等)提供了React特性的真正实现以及平台相关的逻辑。有一些代码是共享的(reconciler)但是这只是在众多独立渲染器中一个实现。


    现在我们知道为什么React和 React-dom 需要同时更新新的特性。举个例子,当React 16.3 增加新的 Context Api的时候,React.createContext()会暴露出来。

    但是 React.createContext() 不会真正的实现 context(上下文) 这个特性。举个例子来说,React DOM 和 React DOM Server 的实现是需要不同的。所以 createContext() 返回了一些 普通对象:

    1. // 相比源码有一些简化
    2. function createContext(defaultValue) {
    3. let context = {
    4. _currentValue: defaultValue,
    5. Provider: null,
    6. Consumer: null
    7. };
    8. context.Provider = {
    9. $$typeof: Symbol.for('react.provider'),
    10. _context: context
    11. };
    12. context.Consumer = {
    13. $$typeof: Symbol.for('react.context'),
    14. _context: context,
    15. };
    16. return context;
    17. }

    当你在代码中使用 或者 的时候,是渲染器决定如何处理它们的。React DOM会使用它的方式来跟踪 context(上下文)的值,而React DOM Server可能实现完全不同。

    所以如果你更新 react 版本到 16.3 及以上但是没有升级 react-dom的版本,你会使用无法感知 Provier 和 Consumer 类型的渲染器。这也是为什么老版本的 react-dom 会报错:这些类型是非法的。

    相同的警告也应用于 React Native。然而,不像 React DOM, React新版本的发布不会马上 “强制”React Native发布新版本。他们有一个独立的发布周期表。每隔几周更新的渲染器代码会分开一次性同步到React Native的代码库。这也是为什么React Native新特性的更新出现和React DOM的周期是不同。