原文: How Does setState Know What to Do?html
原译文: react的setState如何知道他要作什么react
译:可能看到标题的时候会想,怎么去作还不是看代码吗?react中的
setState
不就是负责更新状态码?因而就抱着好奇心看下去了。git
当你在组件中调用setState
的时候,你认为让发生了什么?github
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}
状态的时候re-render
组件而且更新DOM
去返回<h1>Thanks</h1>
元素。编程
看起来很简单,对吧?等一下,请考虑下,这个是react
在处理?或者是ReactDOM
在处理?react-native
更新DOM
听起来彷佛是React DOM
在负责处理。可是咱们调用的是this.setState
,这个api
是来自react
,并不是是React DOM
。而且咱们的React.Component
是定义在React
里的。api
因此React.Component.prototype.setState()
是如何去更新DOM
的。数组
事先声明: 就像本博客大多数的其余的文章同样,你其实能够彻底不用知道这些内容,同样能够很好的使用react
。这系列的文章是针对于那些好奇react
内部原理的一些人。因此读不读,彻底取决于你。dom
咱们可能会认为React.Component
里包含了DOM
的一些更新逻辑。ide
可是若是是咱们猜测的这样,那么this.setState()
如何在其余环境中正常工做?好比在React Native中的组件也是继承于React.Component
, React Native应用像上面同样调用this.setState()
,但React Native可使用Android和iOS原生的视图而不是DOM。
再好比,你可能也熟悉React Test Renderer或Shallow Renderer。这些均可以让你渲染普通组件并在其中调用this.setState()
。可是他们都不适用于DOM
。
若是你使用过像React ART这样的渲染器,你便会知道能够在页面上使用多个渲染器。(例如,ART 组件在React DOM
中工做)。这使得全局标志或变量没法工做。
因此**React.Component
以某种方式委托处理状态更新到特定的平台。** 在咱们理解这是如何发生以前,让咱们深刻了解包的分离方式和缘由。
有一种常见的误区就是React
“引擎(engine)”存在React
包中。这实际上是不对的。
事实上,自从React 0.14拆分包以来,react包只是暴露了用于定义组件的API。React的大多数实现都在“渲染器(renderers)”中。
react-dom
,react-dom / server
,react-native
,react-test-renderer
,react-art
是在renderers
中的一些例子(你也能够创建属于你本身的)。
因此,不管在什么平台,react
包均可以正常工做。他对外暴露的全部的内容,例如: React.Component
,React.createElement
,React.Children
的做用和Hook,都独立于目标平台。不管运行React DOM
,React DOM Server
仍是React Native
,组件都将以相同的方式导入和使用它们。
相比之下,renderer
包对外暴露了特定于平台的api
,像ReactDOM.render
就可让你把React
的层次结构挂载到DOM
节点。每一个renderer
都提供了像这样的API
。理想状况下,大多数组件不须要从renderer
导入任何内容。这使它们更轻便易用。
大多数人都认为react的"引擎(engine)"在每一个renderer
中。 许多renderer
都包含相同代码的副本 - 咱们将其称为“reconciler(和解)”。构建步骤将reconciler(和解)的代码与renderer(渲染器)代码一块儿成为一个高度优化的捆绑包,以得到更好的性能。(复制代码对于包大小一般不是很好,但绝大多数React用户一次只须要一个渲染器,例如react-dom。)
这里要说的是,react包只容许你使用React功能,但不知道它们是如何实现的。renderer
包(react-dom
,react-native
等)提供了React功能和特定于平台的逻辑的实现。其中一些代码是共享的(“reconciler”),但这是各个渲染器的实现细节。
如今咱们知道了为何每次有新的功能都会同时更新react
和react-dom
包。例如,当React 16.3
添加了Context API
时,React.createContext()
在React包上对外暴露。
可是React.createContext
实际上并无实现上下文功能。例如,React DOM
和React DOM Server
之间的实现须要有所不一样。因此createContext()
返回一些普通对象:
// 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>
时,这就由renderer
去决定如何处理他们。React DOM
可能以一种方式跟踪上下文值,但React DOM Server
可能会采用不一样的方式。
因此若是你更新react
到16.3+,可是没有更新react dom
, 那么你使用的这个renderer
将是一个没法解析Provider
和Consumer
类型的renderer
。 这就是为何旧的react-dom
会失败报错这些类型无效。
一样的警告也适用于React Native。可是,与React DOM不一样,React的版本更新不会迫使React Native的版本去当即更新。他有一个本身的发布周期。几周后,更新过的renderer
会单独同步到React Native库中。这就是为何React Native和React DOM可用功能的时间不一致的区别
好吧,如今咱们知道了React包中不包含咱们感兴趣的内容,并且这些实现是存在于像react-dom
, react-native
这样的renderer
中。可是这些并不能回答咱们的问题 -- React.Component
中的setState
是如何知道他要干什么的(与对应的renderer
协同工做)。
答案是在每一个建立renderer
的类上设置一个特殊的字段。 这个字段就叫作updater
。这不是你想要设置啥就设置啥,你不能够设置他,而是要在类的实例被建立后再去设置React DOM
,React DOM Server
或React Native
:
// 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 DOM Server 可能想要忽略状态的更新而且给你一个警告,然而React DOM和React Native会拷贝一份reconciler
(和解)代码去处理他
这就是为何this.setState
定义在React
的包中仍然能够更新DOM
的缘由。他经过读取this.updater
去获取,若是是React DOM
, 就让React DOM
调度并处理更新。
咱们如今知道了类的操做方式,那么hooks
呢?
当大多数的人看到Hooks
提案的API
时,他们经常想知道: useState
是怎么'知道该去作什么’?假设他会比this.setState
更加神奇。
可是正如咱们如今所看到的这个样子,对于理解setState
的实现一直是一种错觉。他除了会将调用做用到对应的renderer
以外不会再作其余任何的操做。实际上useState
这个Hook
作了一样的事情。
相对于setState
的updater
字段而言,Hooks
使用dispatcher
对象。 当调用React.useState
,React.useEffect
或其余内置的Hook
时,这些调用将转发到当前调度程序(dispatcher
)。
// 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);
},
// ...
};
复制代码
而且在渲染你的组件以前,各个renderer
会设置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
这样的renderer
须要访问你调用Hooks
的同一个React包的缘由。不然,你的组件将不会知道dispatcher
!当在同一组件树中有多个React副本时,这可能不会如期工做。可是,这会致使一些那难以理解的错误,因此Hook
会迫使解决包重复问题。
虽然咱们不鼓励你这样作,可是对于高级工具用例,你能够在此技术上重写dispatcher
。(我对__currentDispatcher
这名称撒了谎,这个不是真正的名字,但你能够在React
库中找到真正的名字。)例如,React DevTools将使用特殊的专用dispatcher程序经过捕获JavaScript堆栈跟踪来反思Hooks
树。不要本身在家里重复这个。
这也意味着Hooks
自己并不依赖于React。若是未来有更多的库想要重用这些原始的Hook
,理论上dispatcher
能够移动到一个单独的包中,并做为一个普通名称的API对外暴露。在实践中,咱们宁愿避免过早抽象,直到须要它的时候再说。
updater
字段和__currentDispatcher
对象都是一种称为依赖注入的编程原则的形式。在这些状况下,renderer
注入一些好比setState
这样的功能到React的包中,这样来保持组件更具备声明性。
在使用react
的时候,你不须要去考虑他的工做原理。咱们但愿React用户花更多时间考虑他们的代码而不是像依赖注入这样的抽象概念。可是若是你想知道this.setState
或useState
如何知道该怎么作,我但愿本文会对你有所帮助。