React 中的宝藏:steState 函数

jeot4544 7年前
   <p>React 已经在 JavaScript 中普及了函数式编程。 这导致一些大型框架采用了 React 使用的基于组件的 UI 模式。 现在功能性发烧已经蔓延到整个网络开发生态系统中。</p>    <p>但 React 团队并没有停下脚步,他们继续深入挖掘,发现更多隐藏在神奇库中的强大函数。</p>    <p>所以今天我向你透露一个新函数,它是 React 中的黄金宝藏 —— setState!</p>    <p>它并不是全新的,只是内置于 React 的模式,难以被开发者发掘。</p>    <p>Dan Abramov 将 Functional setState 模式描述为:</p>    <p>与组件类分开声明状态更改。</p>    <h3>基本概念</h3>    <p>React 是一个基于组件的 UI 库。组件是一个接受一些属性并返回一个 UI 元素的函数。</p>    <pre>  <code class="language-javascript">function User(props){    return(      <div>漂亮的使用者</ div>    );  }}</code></pre>    <p>组件需要具有和管理其状态的能力。在这种情况下,通常将组件写为类。constructor 函数中就有了它的状态:</p>    <pre>  <code class="language-javascript">class User {    constructor(){      this.state = {        分数:0      };    } render(){      return(        <div>此用户评分为{this.state.score} </ div>      );    }}  }}</code></pre>    <p>为了管理状态,React 提供了一个名为 setState()的特殊方法:</p>    <pre>  <code class="language-javascript">class User {    ... increaseScore(){      this.setState({score:this.state.score + 1});    } ...  }}</code></pre>    <p>注意 setState()的工作方式。你传递一个对象,其中包含你要更新的状态的一部分。换句话说,传递的对象将具有与组件状态中的键相对应的键,然后 setState()通过将对象合并到状态来更新或设置状态。因此叫“ 状态设置(set-State) ”。</p>    <h3>你可能不知道的部分</h3>    <p>还记得我们介绍的 setState() 的工作原理嘛? 除了其中的传递对象,我们还可以传递函数。</p>    <p>setState() 也接受一个函数作为参数。 该函数获取组件的 previous 状态和 current 属性,用于计算和返回下一个状态。 请看下面代码:</p>    <pre>  <code class="language-javascript">this.setState(function (state, props) {   return {    score: state.score - 1   }  });</code></pre>    <p>注意 setState() 是一个函数,我们将另一个函数传递给它作为参数(函数式编程...函数式 setState)。</p>    <p>初看这一操作似乎并不怎么样,调用 set-state 需要太多步骤了。那为什么要这样做呢?</p>    <h3>为什么要给 setState 传递函数?</h3>    <p>想想当 setState ()  被调用时发生了些什么。 React 首先将你传递给 setState() 函数的对象合并到当前状态中。然后它将开始协调各部分。它将创建一个 React 元素树(用于表示 UI 的对象),对比新旧树的差别,并根据你传递给 setState() 函数对象的不同,计算出相应的变化部分,最终完成 DOM 更新。</p>    <p>工作量真大!事实上,这仅仅是一个简化版的总结。</p>    <p>React 并不只是简单的“set-state”。</p>    <p>因为包含大量的工作,调用 setState() 可能并不会立即更新你的 state。</p>    <p>React 可能会出于性能考虑将多个 setState() 调用合并成一个批处理更新操作。</p>    <p>这样做对 React 而言意味着什么呢?</p>    <p>首先,“多个 setState() 调用”可能意味着在一个单独的函数中调用 setState() 函数多于一次,如下代码:</p>    <pre>  <code class="language-javascript">...  state = {score : 0};  // multiple setState() calls  increaseScoreBy3 () {   this.setState({score : this.state.score + 1});   this.setState({score : this.state.score + 1});   this.setState({score : this.state.score + 1});  }...</code></pre>    <p>现在 React 遇到“多次 setState() 调用”时, 不会真的三次完完整整地去“set-state"——它才不会像我上面说的那样去做如此庞大工作量的事情呢!它会对自己说:“不!我不想每次爬山时只带着一部分状态更新,然后去爬三次山。我更想要有一个容器,用来把这些状态一起打包全放在里面,然后一次性地把它们都带到山上去!”而这就是“批处理”(batching)。</p>    <p>我们传给 setState() 的是一个朴素的对象(plain object)。现在,假设任何时候当 React 遇到“多次 setState() 调用”时,它都会执行批处理,即提取所有单次传递给 setState() 的对象,把它们合并在一起形成一个新的单一的对象,并用这个单一的对象去做 setState() 的事情。</p>    <p>在 JavaScript 里面,合并对象可能会如同下面这种形式:</p>    <pre>  <code class="language-javascript">const singleObject = Object.assign(    {},     objectFromSetState1,     objectFromSetState2,     objectFromSetState3  );</code></pre>    <p>上面这种模式就是我们所熟知的对象组合(object composition)。</p>    <p>JavaScript 中,合并(merging)或组合(composing)对象是这样工作的:如果三个对象有相同的 key, 传给 Object.assign() 的最后一个包含此 key 的对象会覆盖掉前面的值。例如:</p>    <pre>  <code class="language-javascript">const me  = {name : "Justice"},         you = {name : "Your name"},        we  = Object.assign({}, me, you);we.name === "Your name"; //true  console.log(we); // {name : "Your name"}</code></pre>    <p>由于 you 是最后一个合并入 we 的对象, 所以 you 里的 name 值—— Your name 会覆盖掉 me 对象中的 name 值。 所以呢,you 赢啦!:)</p>    <p>你看,当你多次往 setState() 里传入对象调用此方法时,是每次传一个对象,React 会把这些对象合并。换句话说,它会基于我们传入的这多个对象来组合出一个新的对象。并且如果这多个对象有相同的 key, 最后一次传入的对象的 key 值会被储存下来。对吗?</p>    <p>这意味着,以我们上面的 increaseScoreBy3 函数为例,函数的最终结果将只是 1 而不是 3,因为 React 没有立即按照我们调用 setState() 的顺序来更新 state。但是首先,React 将所有对象组合在一起,结果是:{score:this.state.score + 1},那么使用新组合的对象仅完成一次“set-state”。 像这样:User.setState({score:this.state.score + 1}。</p>    <p>更明确来说,将对象传递给 setState() 并不是问题所在。真正的问题在于当你想从前一个状态计算下一个状态时,传递给 setState() 的对象。这么做的风险大,理应停止。</p>    <p>因为 this.props 和 this.state 可能会异步更新,所以你不应该依赖它们的值来计算下一个状态。</p>    <h2>函数式的setState能拯救世界</h2>    <p><a href="/misc/goto?guid=4959741697166240537" rel="nofollow,noindex">Sophia Shoemaker</a> 在 Pen 中演示了这个问题:</p>    <ul>     <li> <p><a href="/misc/goto?guid=4959741697251641316" rel="nofollow,noindex">http://codepen.io/mrscobbler/pen/JEoEgN/</a></p> </li>    </ul>    <p>点击查看这一 Pen 能帮助你更好地了解这个问题。当你查看后你会发现 setState 解决了我们的问题。但是确切地说,是怎么解决的呢?</p>    <p>“状态更新”会被排列,然后按照它们被调用的顺序来执行。</p>    <p>所以,当 React 碰到“多次 setState() 调用”的情况时,它不会把对象合并在一起(当然了,这里并没有什么对象要被合并),它会按照调用的顺序把这些方法排个队。</p>    <p>接下来,React 依次调用队列中的方法,把上一个状态传递给当前的方法,从而不断更新状态。这里提到的“上一个状态”,分两种情况:</p>    <ul>     <li> <p>对队列中第一个被执行的 setState() 而言,那就是在其被执行前的对象的本来状态</p> </li>     <li> <p>对队列中非第一个 setState() 而言,那就是队列里离它最近的 setState() 执行后生成的对象的状态。</p> </li>    </ul>    <p>我想直接拿代码来阐释。但这次只是模拟,目的是传达 React 的操作。</p>    <p>为避免冗杂,我将使用 ES6 语法。</p>    <p>首先,我们创建一个组件类。然后,在这个类里,我们创建一个 setState() 方法。同时,我们的组件还有一个 increaseScoreBy3() 方法——该方法多次调用 setState。最后,如同 React 一样,我们会实例化这个类。</p>    <pre>  <code class="language-javascript">class User{    state = {score : 0};  //let's fake setState    setState(state, callback) {      this.state = Object.assign({}, this.state, state);      if (callback) callback();    }  // multiple functional setState call    increaseScoreBy3 () {      this.setState( (state) => ({score : state.score + 1}) ),      this.setState( (state) => ({score : state.score + 1}) ),      this.setState( (state) => ({score : state.score + 1}) )    }  }const Justice = new User();</code></pre>    <p>我们看到,setState 还会接收一个可选的第二参数——回调函数。如果存在一个回调函数作为入参,React 会在更新完状态后调用该回调函数。</p>    <p>现在,如果一个用户触发 increaseScoreBy3(), React 会把多个 setState 调用进行排列。我就不在这里写伪逻辑了,因为我们关注的是“到底什么东西让 setState 很安全”。不过你可以把这个“排列”的结果想象成一个方法数组,就像这样:</p>    <pre>  <code class="language-javascript">const updateQueue = [    (state) => ({score : state.score + 1}),    (state) => ({score : state.score + 1}),    (state) => ({score : state.score + 1})  ];</code></pre>    <p>最后,我们假设更新过程:</p>    <pre>  <code class="language-javascript">// recursively update state in the order  function updateState(component, updateQueue) {    if (updateQueue.length === 1) {      return component.setState(updateQueue[0](component.state));    }return component.setState(      updateQueue[0](component.state),       () =>       updateState( component, updateQueue.slice(1))     );  }updateState(Justice, updateQueue);</code></pre>    <p>这里的关键重点是每次 React 从 setState 执行函数,并通过传递已更新状态的新副本来更新您的状态。 这使得功能 setState 可以基于先前状态设置状态。</p>    <p>这里我用完整的代码做了一个 bin。 Tinker 只为了使代码更完善。</p>    <p> </p>    <p>来自:https://www.oschina.net/translate/functional-setstate-is-the-future-of-react</p>    <p> </p>