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

<blockquote>本文翻译自:<a href="https://overreacted.io/how-does-setstate-know-what-to-do/" rel="nofollow noreferrer">How Does setState Know What to Do?</a><p>原做者:<a href="https://mobile.twitter.com/dan_abramov" rel="nofollow noreferrer">Dan Abramov</a></p> <p>若是有任何版权问题,请联系<a href="mailto:shuirong1997@icloud.com">shuirong1997@icloud.com</a></p> </blockquote> <p>当你在组件中调用<code>setState</code>时,你以为会发生什么?</p> <pre><code class="react">import React from 'react'; import ReactDOM from 'react-dom';html

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> ); } }react

ReactDOM.render(<Button />, document.getElementById('container'));git

<p>固然,React会用<code>{ clicked: true}</code> 这条状态从新渲染组件而且更新匹配到的DOM,而后返回<code>&lt;h1&gt;Thanks&lt;/h1&gt;</code>元素。</p>
<p>听起来彷佛简洁明了。但别急,React(或者说React DOM)是怎么作的?</p>
<p>更新DOM听起来像是React DOM的事儿,但别忘了咱们调用的但是<code>this.setState()</code>,它是React的东西,可不是React DOM的。另外,咱们的基类<code>React.Component</code>是被定义在React内部。</p>
<p>因此问题来了:<code>React.Component</code>内部的<code>setState</code>怎么能去更新DOM呢?</p>
<p><strong>事先声明:就像个人<a href="https://overreacted.io/how-does-react-tell-a-class-from-a-function/" rel="nofollow noreferrer">其余博客</a>,你不须要熟练掌握React。这篇博客是为那些想要看看面纱以后是什么东西的人准备的。彻底可选!</strong></p>
<hr>
<p>咱们或许会认为<code>React.Component</code>类已经包含了DOM更新逻辑。</p>
<p>但若是这是事实,那<code>this.setState</code>是如何工做在其余环境中呢?好比:在React Native App中的组件也能继承<code>React.Component</code>,他们也能像上面同样调用<code>this.setState()</code>,而且React Native工做在Android和iOS的原生视图而不是DOM中。</p>
<p>你可能也对React Test Renderer 或 Shallow Renderer比较熟悉。这两个测试渲染器让你能够渲染通常的组件而且也能在他们中调用<code>this.setState</code>,但他们可都不使用DOM。</p>
<p>若是你以前使用过一些渲染器好比说<a href="https://github.com/facebook/react/tree/master/packages/react-art" rel="nofollow noreferrer">React ART</a>,你可能知道在页面中使用超过一个渲染器是没什么问题的。(好比:ART组件工做在React DOM  树的内部。)这会产生一个不可维持的全局标志或变量。</p>
<p><strong>因此<code>React.Component</code>以某种方式将state的更新委托为具体的平台(译者注:好比Android, iOS)</strong>,在咱们理解这是如何发生以前,让咱们对包是如何被分离和其缘由挖得更深一点吧!</p>
<hr>
<p>这有一个常见的错误理解:React "引擎"在<code>react</code>包的内部。这不是事实。</p>
<p>事实上,从 <a href="https://reactjs.org/blog/2015/07/03/react-v0.14-beta-1.html" rel="nofollow noreferrer">React 0.14</a>开始对包进行分割时,<code>React</code>包就有意地仅导出关于如何定义组件的API了。React的大部分实现其实在“渲染器”中。</p>
<p>渲染器的其中一些例子包括:<code>react-dom</code>,<code>react-dom/server</code>,<code>react-native</code>,<code>react-test-renderer</code>,<code>react-art</code>(另外,你也能够<a href="https://github.com/facebook/react/blob/master/packages/react-reconciler/README.md" rel="nofollow noreferrer">构建本身的</a>)。</p>
<p>这就是为何<code>react</code>包帮助很大而无论做用在什么平台上。全部它导出的模块,好比<code>React.Component</code>,<code>React.createElement</code>,<code>React.Children</code>和<code>[Hooks](https://reactjs.org/docs/hooks-intro.html)</code>,都是平台无关的。不管你的代码运行在React DOM、React DOM Server、仍是React Native,你的组件均可以以一种相同的方式导入而且使用它们。</p>
<p>与之相对的是,渲染器会暴露出平台相关的接口,好比<code>ReactDOM.render()</code>,它会让你能够把React挂载在DOM节点中。每一个渲染器都提供像这样的接口,但理想状况是:大多数组件都不须要从渲染器中导入任何东西。这能使它们更精简。</p>
<p><strong>大多数人都认为React“引擎”是位于每一个独立的渲染器中的</strong>。许多渲染器都包含一份相同的代码—咱们叫它<a href="https://github.com/facebook/react/tree/master/packages/react-reconciler" rel="nofollow noreferrer">“调节器”</a>,为了表现的更好,遵循<a href="https://reactjs.org/blog/2017/12/15/improving-the-repository-infrastructure.html" rel="nofollow noreferrer">这个步骤</a> 可让调节器的代码和渲染器的代码在打包时归到一处。(拷贝代码一般不是优化“打包后文件”(bundle)体积的好办法,但大多数React的使用者一次只须要一个渲染器,好比:<code>react-dom</code>(译者注:所以能够忽略调节器的存在))</p>
<p>The takeaway here 是<code>react</code>包仅仅让你知道如何使用React的特性而无需了解他们是如何被实现的。渲染器(<code>react-dom,react-native</code>等等)会提供React特性的实现和平台相关的逻辑;一些关于调节器的代码被分享出来了,但那只是单独渲染器的实现细节而已。</p>
<hr>
<p>如今咱们知道了为何<code>react</code>和<code>react-dom</code>包须要为新特定更新代码了。好比:当React16.3新增了Context接口时,<code>React.createContext()</code>方法会在React包中被暴露出来。</p>
<p>可是<code>React.createContext()</code>实际上不会实现具体的逻辑(译者注:只定义接口,由其余渲染器来实现逻辑)。而且,在React DOM和React DOM Server上实现的逻辑也会有区别。因此<code>createContext()</code>会返回一些纯粹的对象(定义如何实现):</p>
<pre><code class="react">// 一个简单例子
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;
}

<p>你会在某处代码中使用<code>&lt;MyContext.Provider&gt;</code>或<code>&lt;MyContext.Consumer</code>&gt;,那里就是决定着如何处理他们的渲染器。React DOM会用A方法追踪context值,但React DOM Server或许会用另外一个不一样的方法实现。</p> <p><strong>因此若是你将<code>react</code>升级到16.3+,但没有升级react-dom,你将使用一个还不知道<code>Provider</code>和<code>Consumer</code>类型的渲染器</strong>,这也就旧版的<code>react-dom</code>可能会<a href="https://stackoverflow.com/a/49677020/458193" rel="nofollow noreferrer">报错:fail saying these types are invalid</a>的缘由。</p> <p>一样的警告也会出如今React Native中,可是不一样于React DOM,一个新的React版本不会当即产生一个对应的React Native版本。他们(React Native)有本身的发布时间表。大概几周后,渲染器代码才会<a href="https://github.com/facebook/react-native/commits/master/Libraries/Renderer/oss" rel="nofollow noreferrer">单独更新</a>到React Native库中。这就是为何新特性在React Native生效的时间会和React DOM不一样。</p> <hr> <p>Okay,那么如今咱们知道了<code>react</code>包不包含任何好玩的东西,而且具体的实现都在像<code>react-dom</code>,<code>react-native</code>这样的渲染器中。但这并不能回答咱们开头提出的问题。<code>React.Component</code>里的<code>setState()</code>是如何和对应的渲染器通讯的呢?</p> <p><strong>答案是每一个渲染器都会在建立的类中添加一个特殊的东西</strong>,这个东西叫<code>updater</code>。它不是你添加的东西—偏偏相反,它是React DOM,React DOM Server 或者React Native在建立了一个类的实例后添加的:</p> <pre><code class="react">// 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; ```github

<p>从 <a href="https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react/src/ReactBaseClasses.js" rel="nofollow noreferrer"><code>setState</code>的实现</a>就能够看出,它作的全部的工做就是把任务委托给在这个组件实例中建立的渲染器:</p> <pre><code class="react">// 简单例子 setState(partialState, callback) { // 使用`updater`去和渲染器通讯 this.updater.enqueueSetState(this, partialState, callback); } ```segmentfault

<p>React DOM Server <a href="https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-dom/src/server/ReactPartialRenderer.js" rel="nofollow noreferrer">可能想</a>忽略状态更新而且警告你,然而React DOM和React Native将会让调节器的拷贝部分去 <a href="https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-reconciler/src/ReactFiberClassComponent.js" rel="nofollow noreferrer">处理它</a>。</p> <p>这就是尽管<code>this.setState()</code>被定义在React包中也能够更新DOM的缘由。它调用被React DOM添加的<code>this.updater</code>而且让React DOM来处理更新。</p> <hr> <p>如今咱们都比较了解“类”了,但“钩子”(Hooks)呢?</p> <p>当人们第一次看到 <a href="https://reactjs.org/docs/hooks-intro.html" rel="nofollow noreferrer">钩子接口的提案</a>时,他们常回想:<code>useState</code>是怎么知道该作什么呢?这一假设简直比对<code>this.setState()</code>的疑问还要迷人。</p> <p>但就像咱们现在看到的那样,<code>setState()</code>的实现一直以来都是模糊不清的。它除了传递调用给当前的渲染器外什么都不作。因此,<code>useState</code>钩子作的事也是如此。</p> <p>此次不是<code>updater</code>,钩子(Hooks)使用一个叫作“分配器”(dispatcher)的对象,当你调用<code>React.useState()</code>、<code>React.useEffect()</code>或者其余自带的钩子时,这些调用会被推送给当前的分配器。</p> <pre><code class="react">// In React (simplified a bit) const React = { // Real property is hidden a bit deeper, see if you can find it! __currentDispatcher: null,react-native

useState(initialState) { return React.__currentDispatcher.useState(initialState); },数组

useEffect(initialState) { return React.__currentDispatcher.useEffect(initialState); }, // ... };dom

<p>单独的渲染器会在渲染你的组件以前设置分配器(dispatcher)。</p>
<pre><code class="react">// In React DOM
const prevDispatcher = React.__currentDispatcher;
React.__currentDispatcher = ReactDOMDispatcher;let result;
try {
  result = YourComponent(props);
} finally {
  // Restore it back  React.__currentDispatcher = prevDispatcher;}

<p>React DOM Server的实如今<a href="https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-dom/src/server/ReactPartialRendererHooks.js" rel="nofollow noreferrer">这里</a>。由React DOM和React Native共享的调节器实如今<a href="https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-reconciler/src/ReactFiberHooks.js" rel="nofollow noreferrer">这里</a>。</p> <p>这就是为何像<code>react-dom</code>这样的渲染器须要访问和你调用的钩子所使用的<code>react</code>同样的包。不然你的组件将找不到分配器!若是你有<a href="https://github.com/facebook/react/issues/13991" rel="nofollow noreferrer">多个React的拷贝在相同的组件树中</a>,代码可能不会正常工做。然而,这老是形成复杂的Bug,所以钩子会在它耗光你的精力前强制你去解决包的副本问题。</p> <p>若是你不以为这有什么,你能够在工具使用它们前精巧地覆盖掉原先的分配器(<code>__currentDispatcher</code>的名字其实我本身编的但你能够在React仓库中找到它真正的名字)。好比:React DevTools会使用一个<a href="https://github.com/facebook/react/blob/ce43a8cd07c355647922480977b46713bd51883e/packages/react-debug-tools/src/ReactDebugHooks.js" rel="nofollow noreferrer">特殊的内建分配器</a>来经过捕获JavaScript调用栈来反映(introspect)钩子。不要在家里重复这个(Don’t repeat this at home.)(译者注:多是“不要在家里模仿某项实验”的衍生体。多是个笑话,但我get到)</p> <p>这也意味着钩子不是React固有的东西。若是在未来有不少类库想要重用相同的基础钩子,理论上来讲分配器可能会被移到分离的包中而且被塑形成优秀的接口—会有更少让人望而生畏的名称—暴露出来。在实际中,咱们更偏向去避免过于仓促地将某物抽象,直到咱们的确须要这么作。</p> <p><code>updater</code>和<code>__currentDispatcher</code>都是泛型程序设计(<em>依赖注入</em>/<em>dependency injection</em>)的绝佳实例。渲染器“注入”特性的实现。就像<code>setState</code>可让你的组件看起来简单明了。</p> <p>当你使用React时,你不须要考虑它是如何工做的。咱们指望React用户去花费更多的时间去考虑它们的应用代码而不是一些抽象的概念好比:依赖注入。但若是你曾好奇<code>this.setState()</code>或<code>useState()</code>是怎么知道它们该作什么的,那我但愿这篇文章将帮助到你。</p>ide

来源:http://www.javashuo.com/article/p-ukltowzg-gv.html工具

相关文章
相关标签/搜索