内容简介:在组件中调用当然,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 DOM
、 React 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
程序的实现细节。
现在我们知道了为什么需要为新特性更新 react
和 react-dom
包。例如,当React 16.3中添加了 Context API
后, React.createContext()
就在React包中被暴露出来。
但是 React.createContext()
没有真正实现context特性。例如,在 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;
}
复制代码
当你在代码中使用 <MyContext.Provider>
或 <MyContext.Comsumer>
时,是 renderer
决定如何处理它们。 React DOM
可能以一种方式跟踪 context
的值,但 React DOM Server
可能采用不同的方式。
因此,如果你更新 react
到16.3+,但不更新 react-dom
,那么你将使用一个没有意识到特定的 Provider
和 Consumer
类型的 renderer
。这就是为什么旧的 react-dom
无法说这些类型是失效的
。
同样的警告可以运用到 React Native
中。然而,不像 React DOM
, React
的版本发布不会立即“强制” React Native
的版本发布。它们有独立的版本发布表。更新后的 renderer
程序代码每隔几周就会被单独同步到 React
本地存储库中一次。这就是为什么 React Native
中的特性与 React DOM
中的特性在时间表上有所不同的原因。
好了,现在我们知道了 react
包没有包含任何有趣的东西,并且它的实现都存在与 renderers
中像 react-dom
, react-native
等等。但是这并没有回答我们的问题。 React.Component
中的 setState()
是如何和正确的 renderer
“交谈”的?
**答案是每个 renderer
在创建的类上设置一个特殊的字段。**这个特殊字段被称作 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
在 React.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()
如何知道该做什么的,我希望这能有所帮助。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 谈谈大家想知道的、不知道的SDN
- 你知道和你不知道的冒泡排序
- JS数组中那些你知道或不知道的
- 掌握Python列表理解需要知道的9件事,你知道吗?
- 你所知道或不知道的CSS content属性
- 前端程序员不知道的14个JavaScript调试技巧,你知道几个?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
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 互转工具