setState如何知道该做什么?

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

当然是:React根据下一个状态{clicked:true}重新渲染组件,同时更新DOM以匹配返回的<h1>Thanks</ h1>元素啊。

看起来很直白。但是等等,是 React做了这些吗?还是React DOM?

更新DOM听起来像是React DOM的职责所在。但是我们调用的是this.setState(),而没有调用任何来自React DOM的东西。而且我们组件的父类React.Component也是在React本身定义的。

所以存在于React.Component内部的setState()是如何更新DOM的呢?

我们或许会认为:React.Component类包含了DOM更新的逻辑。

但是如果是这样的话,this.setState()又如何能在其他环境下使用呢?举个例子,React Native app中的组件也是继承自React.Component。他们依然可以像我们在上面做的那样调用this.setState(),而且React Native渲染的是安卓和iOS原生的界面而不是DOM。

因此,React.Component以某种未知的方式将处理状态(state)更新的任务委托给了特定平台的代码。在我们理解这些是如何发生的之前,让我们深挖一下包(packages)是如何分离的以及为什么这样分离。

一个很常见的误解就是React“引擎”是存在于react包里面的。然而事实并非如此。

实际上从React 0.14代码拆分成多个包以来,react包故意只暴露一些定义组件的API。绝大多数React的实现都存在于“渲染器(renderers)”中。

react-dom、react-dom/server、 react-native、 react-test-renderer、 react-art都是常见的渲染器(当然你也可以创建属于你的渲染器)。

这就是为什么不管你的目标平台是什么,react包都是可用的。从react包中导出的一切,比如React.ComponentReact.createElement、 React.Children 和(最终的)Hooks,都是独立于目标平台的。无论你是运行React DOM,还是 React DOM Server,或是 React Native,你的组件都可以使用同样的方式导入和使用。

相比之下,渲染器包暴露的都是特定平台的API,比如说:ReactDOM.render(),可以让你将React层次结构(hierarchy)挂载进一个DOM节点。每一种渲染器都提供了类似的API。理想状况下,绝大多数组件都不应该从渲染器中导入任何东西。只有这样,组件才会更加灵活。

和大多数人现在想的一样,React “引擎”就是存在于各个渲染器的内部。很多渲染器包含一份同样代码的复制 —— 我们称为“协调器”(“reconciler”)。构建步骤(build step)将协调器代码和渲染器代码平滑地整合成一个高度优化的捆绑包(bundle)以获得更高的性能。

这里要注意的是:react包仅仅是让你使用React 的特性,但是它完全不知道这些特性是如何实现的。而渲染器包(react-dom、react-native等)提供了React特性的实现以及平台特定的逻辑。这其中的有些代码是共享的(“协调器”),但是这就涉及到各个渲染器的实现细节了。

现在我们知道为什么当我们想使用新特性时,react 和 react-dom都需要被更新。举个例子,当React 16.3添加了Context API,React.createContext()API会被React包暴露出来。

但是React.createContext() 其实并没有实现 context。因为在React DOM 和 React DOM Server 中同样一个 API 应当有不同的实现。所以createContext()只返回了一些普通对象:

当你在代码中使用 <MyContext.Provider> 或 MyContext.Consumer>的时候, 是渲染器决定如何处理这些接口。React DOM也许用某种方式追踪context的值,但是React DOM Server用的可能是另一种不同的方式。

所以,如果你将react升级到了16.3+,但是不更新react-dom,那么你就使用了一个未定义 Provider 和 Consumer类型的渲染器。这就是为什么一个老版本的react-dom会报错说这些类型是无效的。

同样的警告也会出现在React Native中。然而不同于React DOM的是, 一个React新版本的发布并不立即“强制”发布新的 React Native 版本。他们具有独立的发布日程。每隔几周,更新后的渲染器代码就会单独同步到React Native仓库。这就是相比 React DOM,React Native 特性可用时间不同的原因。

好吧,所以现在我们知道了react包并不包含任何有趣的东西,除此之外,具体的实现也是存在于react-dom,react-native之类的渲染器中。但是这并没有回答我们的问题。React.Component中的setState()如何与正确的渲染的?

答案是:每个渲染器都在已创建的类上设置了一个特殊的字段。这个字段叫做updater。这并不是你要设置的的东西——而是,React DOM、React DOM Server 或 React Native在创建完你的类的实例之后会立即设置的东西:

查看React.ComponentsetState的实现, setState所做的一切就是委托渲染器创建这个组件的实例:

这就是this.setState()尽管定义在React包中,却能够更新DOM的原因。它读取由React DOM设置的this.updater,让React DOM安排并处理更新。

现在关于类的部分我们已经知道了,那关于Hooks的呢?

当人们第一次看见Hooks API,他们可能经常会想: useState是怎么 “知道要做什么”的?然后假设它比那些包含this.setState()React.Component类更“神奇”。

但是正如我们今天所看到的,基类中setState()的执行一直以来都是一种错觉。它除了将调用转发给当前的渲染器外,什么也没做。useState Hook也是做了同样的事情。

Hooks使用了一个“dispatcher”对象,代替了updater字段。当你调用React.useState()React.useEffect()、 或者其他内置的Hook时,这些调用被转发给了当前的dispatcher

各个渲染器会在渲染你的组件之前设置dispatcher

使用React时,你无需考虑这其中的原理。我们希望React用户花更多时间考虑他们的应用程序代码,而不是像依赖注入这样的抽象概念。但是如果你想知道this.setState()useState()是如何知道该做什么的,我希望这篇文章会有所帮助。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值