内容简介:要有心里准备,这篇文章抽象又拗口,希望有人可以将它视觉化!当你在组件里调用很明显,React会随着新的
要有心里准备,这篇文章抽象又拗口,希望有人可以将它视觉化!
当你在组件里调用 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}
状态重渲染组件(component),更新DOM,匹配返回 <h1>Thanks</h1>
元素(element)。
似乎很简单。不过问题来了,是 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 这样的渲染器(renderer),你可能也知道页面有可能使用多个渲染器(例如,ART组件运行于React DOM树中),这使得全局标志或变量不再可靠。
所以,针对不同平台代码,
React.Component
以某种委托方式处理state更新
。在我们弄清楚怎么回事前,先深入探讨下如何及为什么要分离包(packages)。
有一种常见的误解,即React的“引擎”在 react
依赖包中,这不是真的。
实际上,自从React 0.14拆分依赖包以来, react
依赖包特意地只暴露 定义
组件(components)的APIs,React绝大多数 实现
都放在 “渲染器”,
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,你都可以用同一种方式导入使用组件。
相比之下,渲染器依赖包暴露特定平台的APIs,如 ReactDOM.render()
,可以将React组件插入DOM节点中。每个渲染器都会提供一个类似的API,理想情况下,大多数 组件
不需要从渲染器导入任何内容,这使它们更灵活。
大多数人认为React的“引擎”在每个渲染器中。不过许多渲染器确实包含了同一份副本代码 —— 我们称为 "reconciler"
。有个构建步骤将 reconciler 代码与渲染器代码融合成一份高度优化过的代码,以获得更好的性能。(复制代码通常不利于依赖包大小,但绝大多数用户一次只需要一个渲染器,例如 react-dom
)
这里要说的是, react
依赖包只让你知道React有哪些功能,但不知道功能是如何实现的。渲染器依赖包( react-dom
、 react-native
等)提供了React功能的实现和平台特性的逻辑。其中一些代码是共享的("reconciler"),但更多的是各个渲染器的具体实现。
现在我们知道为什么有功能时, react
和 react-dom
依赖包需要同时更新了,比如说,在React 16.3添加 Context API 时,React依赖包会暴露 React.createContext()
。
但 React.createContext()
实际上并没有 实现
context功能,React DOM 与 React DOM Server 的实现是不同的。例如, createContext
返回一些 plain objects:
// A bit simplified 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.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 Native和React DOM同一个功能,可以用上的时间是不同的。
好了,我们现在知道 react
依赖包不包含任何有趣的内容,因为具体实现放到 react-dom
、 react-native
等渲染器中了。但是这没能解决我们的问题, React.Component
中的 setState()
是如何与对应的渲染器“交流的”。
答案是每个渲染器在创建的class上设置一个特殊字段。这个字段叫做 updater
。这不是由你设置的,而是React DOM、React DOM Server、React Native在你实例class后给你加上的:
// Inside React DOM const inst = new YourComponent(); inst.props = props; inst.updater = ReactDOMUpdater; // Inside React DOM Server const inst = new YourComponent(); inst.props = props; inst.updater = ReactDOMServerUpdater; // Inside React Native const inst = new YourComponent(); inst.props = props; inst.updater = ReactNativeUpdater; 复制代码
查看
React.Component
中的 setState
实现
,它所做的就是将任务全部委托给实例此组件的渲染器:
// 简化后的代码 setState(partialState, callback) { // 用`updater` 反馈给渲染器 this.updater.enqueueSetState(this, partialState, callback); } 复制代码
React DOM Server 也许打算 忽略state更新并警告你,而React DOM和React Native会用复制来的"reconciler"去 处理它 。
这也是为什么即使 this.setState()
定义在React依赖包中,依然可以更新DOM。它会获取由React DOM设置的 this.updater
,并让React DOM调度和处理更新。
我们现在知道class了,那Hooks是怎么做的?
当大家第一次看到Hooks API,很可能会想: useState
怎么“知道该怎么做”?猜想是它的 this.setState()
比基于 React.Component
的更“神奇”。
但正如我们今天看到的,基于class的 setState()
实现一直是一种错觉,除了调用指向当前的渲染器之外,它不参与任何操作。 useState
Hook 也同样如此
。
Hooks使用 dispatcher
对象而不是 updater
字段 。在你调用 React.useState()
、 React.useEffect()
、或者其他内置Hook时,这些都会转发给当前的dispatcher。
// In React (简化) const React = { // 真正的属性隐藏得有点深,你可以尝试去找找看! __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共享的 reconciler 实现在 这儿 。
这就是像 react-dom
这样的渲染器需要获取同一个 react
依赖包的原因,否则,你的组件不会“看到”这个dispatcher!如果在同一棵组件树中存在 多个React副本
,就有可能发生问题。不过这样容易出现隐蔽bug,所以Hooks会强迫你在发生前就解决依赖包重复问题。
虽然我们不鼓励这样做,但为了更适用于某些情景,你可以在技术上自行覆盖dispatcher( __currentDispatcher
是我编造的,不过你可以在代码库中找到真实的名称),例如,React DevTools会用 一个专门定制的dispatcher
通过捕获JavaScript堆栈轨迹来描绘反馈Hooks树。 不要在家重复这样做了
。
这也意味着Hooks本身并不依赖于React。如果将来有更多的类库想复用React里的Hooks理念,理论上dispatcher可以挪过去用并且作为一个更少“可怕”名称的一流API展现出来。在开发过程中,我们应该避免过早抽象概念,直到我们不得不这么做了。
updater
字段和 __currentDispatcher
对象都形成于一个叫 依赖注入
的通用编程原理。这两种情况里,渲染器将诸如 setState
之类的功能实现“注入”到通用的React依赖包中,组件因此以声明为主。
在使用React时,你不需要思考这些是怎么跑起来的。我们希望React开发者花更多的时间在应用程序代码上,而不是像依赖注入这些抽象概念上。但如果你想知道 this.setState()
或者 useState
是如何知道怎么做的,我希望这会有所帮助。
翻译原文 How Does setState Know What to Do? (2018-12-09)
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:- 谈谈大家想知道的、不知道的SDN
- 你知道和你不知道的冒泡排序
- JS数组中那些你知道或不知道的
- 掌握Python列表理解需要知道的9件事,你知道吗?
- 你所知道或不知道的CSS content属性
- 前端程序员不知道的14个JavaScript调试技巧,你知道几个?
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Python编程快速上手
Albert Sweigart / 王海鹏 / 人民邮电出版社 / 2016-7-1 / 69.00元
如今,人们面临的大多数任务都可以通过编写计算机软件来完成。Python是一种解释型、面向对象、动态数据类型的高级程序设计语言。通过Python编程,我们能够解决现实生活中的很多任务。 本书是一本面向实践的Python编程实用指南。本书的目的,不仅是介绍Python语言的基础知识,而且还通过项目实践教会读者如何应用这些知识和技能。本书的首部分介绍了基本Python编程概念,第二部分介绍了一些不......一起来看看 《Python编程快速上手》 这本书的介绍吧!