资讯专栏INFORMATION COLUMN

「译」setState如何知道它该做什么?

OldPanda / 1214人阅读

摘要:本文翻译自原作者如果有任何版权问题,请联系当你在组件中调用时,你觉得会发生什么当然,会用这条状态重新渲染组件并且更新匹配到的,然后返回元素。如果你之前使用过一些渲染器比如说,你可能知道在页面中使用超过一个渲染器是没什么问题的。

本文翻译自:How Does setState Know What to Do?

原作者:Dan Abramov

如果有任何版权问题,请联系shuirong1997@icloud.com

当你在组件中调用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 

Thanks

; } return ( ); } } ReactDOM.render(

当然,React会用{ clicked: true} 这条状态重新渲染组件并且更新匹配到的DOM,然后返回

Thanks

元素。

听起来似乎简洁明了。但别急,React(或者说React DOM)是怎么做的?

更新DOM听起来像是React DOM的事儿,但别忘了我们调用的可是this.setState(),它是React的东西,可不是React DOM的。另外,我们的基类React.Component是被定义在React内部。

所以问题来了:React.Component内部的setState怎么能去更新DOM呢?

事先声明:就像我的其他博客,你不需要熟练掌握React。这篇博客是为那些想要看看面纱之后是什么东西的人准备的。完全可选!

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

但如果这是事实,那this.setState是如何工作在其他环境中呢?比如:在React Native App中的组件也能继承React.Component,他们也能像上面一样调用this.setState(),并且React Native工作在Android和iOS的原生视图而不是DOM中。

你可能也对React Test Renderer 或 Shallow Renderer比较熟悉。这两个测试渲染器让你可以渲染一般的组件并且也能在他们中调用this.setState,但他们可都不使用DOM。

如果你之前使用过一些渲染器比如说React ART,你可能知道在页面中使用超过一个渲染器是没什么问题的。(比如:ART组件工作在React DOM 树的内部。)这会产生一个不可维持的全局标志或变量。

所以React.Component以某种方式将state的更新委托为具体的平台(译者注:比如Android, iOS),在我们理解这是如何发生之前,让我们对包是如何被分离和其原因挖得更深一点吧!

这有一个常见的错误理解:React "引擎"在react包的内部。这不是事实。

事实上,从 React 0.14开始对包进行分割时,React包就有意地仅导出关于如何定义组件的API了。React的大部分实现其实在“渲染器”中。

渲染器的其中一些例子包括:react-dom,react-dom/server,react-native,react-test-renderer,react-art(另外,你也可以构建自己的)。

这就是为什么react包帮助很大而不管作用在什么平台上。所有它导出的模块,比如React.ComponentReact.createElementReact.Children[Hooks](https://reactjs.org/docs/hooks-intro.html),都是平台无关的。无论你的代码运行在React DOM、React DOM Server、还是React Native,你的组件都可以以一种相同的方式导入并且使用它们。

与之相对的是,渲染器会暴露出平台相关的接口,比如ReactDOM.render(),它会让你可以把React挂载在DOM节点中。每个渲染器都提供像这样的接口,但理想情况是:大多数组件都不需要从渲染器中导入任何东西。这能使它们更精简。

大多数人都认为React“引擎”是位于每个独立的渲染器中的。许多渲染器都包含一份相同的代码—我们叫它“调节器”,为了表现的更好,遵循这个步骤 可以让调节器的代码和渲染器的代码在打包时归到一处。(拷贝代码通常不是优化“打包后文件”(bundle)体积的好办法,但大多数React的使用者一次只需要一个渲染器,比如:react-dom(译者注:因此可以忽略调节器的存在))

The takeaway here 是react包仅仅让你知道如何使用React的特性而无需了解他们是如何被实现的。渲染器(react-dom,react-native等等)会提供React特性的实现和平台相关的逻辑;一些关于调节器的代码被分享出来了,但那只是多带带渲染器的实现细节而已。

现在我们知道了为什么reactreact-dom包需要为新特定更新代码了。比如:当React16.3新增了Context接口时,React.createContext()方法会在React包中被暴露出来。

但是React.createContext()实际上不会实现具体的逻辑(译者注:只定义接口,由其他渲染器来实现逻辑)。并且,在React DOM和React 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;
}

你会在某处代码中使用>,那里就是决定着如何处理他们的渲染器。React DOM会用A方法追踪context值,但React DOM Server或许会用另一个不同的方法实现。

所以如果你将react升级到16.3+,但没有升级react-dom,你将使用一个还不知道ProviderConsumer类型的渲染器,这也就旧版的react-dom可能会报错:fail saying these types are invalid的原因。

同样的警告也会出现在React Native中,但是不同于React DOM,一个新的React版本不会立即产生一个对应的React Native版本。他们(React Native)有自己的发布时间表。大概几周后,渲染器代码才会多带带更新到React Native库中。这就是为什么新特性在React Native生效的时间会和React DOM不同。

Okay,那么现在我们知道了react包不包含任何好玩的东西,并且具体的实现都在像react-domreact-native这样的渲染器中。但这并不能回答我们开头提出的问题。React.Component里的setState()是如何和对应的渲染器通信的呢?

答案是每个渲染器都会在创建的类中添加一个特殊的东西,这个东西叫updater。它不是你添加的东西—恰恰相反,它是React DOM,React 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;

setState的实现就可以看出,它做的所有的工作就是把任务委托给在这个组件实例中创建的渲染器:

// 简单例子
setState(partialState, callback) {
  // 使用`updater`去和渲染器通信
  this.updater.enqueueSetState(this, partialState, callback);
}

React DOM Server 可能想忽略状态更新并且警告你,然而React DOM和React Native将会让调节器的拷贝部分去 处理它。

这就是尽管this.setState()被定义在React包中也可以更新DOM的原因。它调用被React DOM添加的this.updater并且让React DOM来处理更新。

现在我们都比较了解“类”了,但“钩子”(Hooks)呢?

当人们第一次看到 钩子接口的提案时,他们常回想:useState是怎么知道该做什么呢?这一假设简直比对this.setState()的疑问还要迷人。

但就像我们如今看到的那样,setState()的实现一直以来都是模糊不清的。它除了传递调用给当前的渲染器外什么都不做。所以,useState钩子做的事也是如此。

这次不是updater,钩子(Hooks)使用一个叫做“分配器”(dispatcher)的对象,当你调用React.useState()React.useEffect()或者其他自带的钩子时,这些调用会被推送给当前的分配器。

// In React (simplified a bit)
const React = {
  // Real property is hidden a bit deeper, see if you can find it!
  __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共享的调节器实现在这里。

这就是为什么像react-dom这样的渲染器需要访问和你调用的钩子所使用的react一样的包。否则你的组件将找不到分配器!如果你有多个React的拷贝在相同的组件树中,代码可能不会正常工作。然而,这总是造成复杂的Bug,因此钩子会在它耗光你的精力前强制你去解决包的副本问题。

如果你不觉得这有什么,你可以在工具使用它们前精巧地覆盖掉原先的分配器(__currentDispatcher的名字其实我自己编的但你可以在React仓库中找到它真正的名字)。比如:React DevTools会使用一个特殊的内建分配器来通过捕获JavaScript调用栈来反映(introspect)钩子。不要在家里重复这个(Don’t repeat this at home.)(译者注:可能是“不要在家里模仿某项实验”的衍生体。可能是个笑话,但我get到)

这也意味着钩子不是React固有的东西。如果在将来有很多类库想要重用相同的基础钩子,理论上来说分配器可能会被移到分离的包中并且被塑造成优秀的接口—会有更少让人望而生畏的名称—暴露出来。在实际中,我们更偏向去避免过于仓促地将某物抽象,直到我们的确需要这么做。

updater__currentDispatcher都是泛型程序设计(依赖注入/dependency injection)的绝佳实例。渲染器“注入”特性的实现。就像setState可以让你的组件看起来简单明了。

当你使用React时,你不需要考虑它是如何工作的。我们期望React用户去花费更多的时间去考虑它们的应用代码而不是一些抽象的概念比如:依赖注入。但如果你曾好奇this.setState()useState()是怎么知道它们该做什么的,那我希望这篇文章将帮助到你。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/100865.html

相关文章

  • []React 的生命周期的使用场景

    摘要:译的生命周期的使用场景原文链接作者翻译上名这个图片,就是组件的生命周期,从形成到销毁的过程。这并不意味着没有用。最常见的用例更新以响应或更改。是否可以调用总结在理想的世界中,我们不会使用生命周期方法。 [译]React 的生命周期的使用场景 showImg(https://segmentfault.com/img/bVLTCt?w=2000&h=800); 原文链接:React Lif...

    klinson 评论0 收藏0
  • React 常见的面试题(在 React 里面,你可以知道也可以不知道的事, 但是你会发现他们确实很

    摘要:为了使用它们,您可以向组件添加一个属性,该属性的值是一个回调函数,它将接收底层的元素或组件的已挂接实例,作为其第一个参数。通常最好使用另一个生命周期方法,而不是依赖这个回调函数,但是很高兴知道它存在。 React 常见的面试题 (在 React 里面,你可以知道也可以不知道的事, 但是你会发现他们确实很有用) 根据记录,问这些问题可能不是深入了解他们在使用 React 方面的经验的最...

    cppprimer 评论0 收藏0
  • 】React 组件的生命周期

    摘要:此篇文章我们将会继续探索组件的特性,特别是生命周期。这些方法叫做组件的生命周期方法且会根据特定并可预测的顺序被调用。基本上所有的组件的生命周期方法都可以被分割成四个阶段初始化挂载阶段更新阶段卸载阶段。 原文:https://medium.com/react-ecosystem/react-components-lifecycle-ce09239010df#.j7h6w8ccc 译者序...

    Crazy_Coder 评论0 收藏0
  • 】Redux 还是 Mobx,让我来解决你的困惑!

    摘要:我现在写的这些是为了解决和这两个状态管理库之间的困惑。这甚至是危险的,因为这部分人将无法体验和这些库所要解决的问题。这肯定是要第一时间解决的问题。函数式编程是不断上升的范式,但对于大部分开发者来说是新奇的。规模持续增长的应 原文地址:Redux or MobX: An attempt to dissolve the Confusion 原文作者:rwieruch 我在去年大量的使用...

    txgcwm 评论0 收藏0
  • 】React及React Fiber基本的设计理念

    摘要:基础的理论概念这篇文章是我的一次尝试,希望能够形式化的介绍关于本身的一些理念模型。我对于此实际的理念模型是在每次的更新过程中返回下一个阶段的状态。的目标是提升对在动画,布局以及手势方面的友好度。我已经邀请了团队的成员来对本文档的准确性进行。 前言 本文主要是对收集到的一些官方或者其他平台的文章进行翻译,中间可能穿插一些个人的理解,如有错误疏漏之处,还望批评指正。笔者并未研究过源码,只是...

    lewif 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<