医者行喀布尔 發表於 2020-4-11 23:32:00

react setState 原理

<ul>
<li><span style="font-size: 18px">组件的数据来源有两个地方,分别是<span style="color: rgba(255, 0, 0, 1)">属性</span>对象和<span style="color: rgba(255, 0, 0, 1)">状态</span>对象</span></li>
<li><span style="font-size: 18px"><span style="color: rgba(255, 0, 0, 1)">属性</span>是父组件传递过来的,不可更改</span></li>
<li><span style="font-size: 18px"><span style="color: rgba(255, 0, 0, 1)">状态</span>是自己内部的,改变状态的唯一方式就是setState</span></li>
<li><span style="font-size: 18px">属性和状态的变化都会引起视图更新</span></li>
</ul>
<div class="cnblogs_code">
<pre><span style="font-size: 18px">import React from "react"<span style="color: rgba(0, 0, 0, 1)">;
import ReactDOM from </span>"react-dom"<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 属性是由父组件传递过来的,不能改变
* 状态是组件内部,由自己维护,外界无法访问改变状态的唯一方式就是setState
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
class Counter extends React.Component{
constructor(props) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造函数是唯一给状态赋值的地方</span>
<span style="color: rgba(0, 0, 0, 1)">    super(props)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义状态的地方</span>
    <span style="color: rgba(255, 0, 0, 1)">this.state</span> = {number: 0<span style="color: rgba(0, 0, 0, 1)">}
}
render(){
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当我们调用setState的时候会引起状态的改变和组件的更新</span>
    console.log('render'<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.number}&lt;/p&gt;
      &lt;button onClick={() =&gt; <span style="color: rgba(255, 0, 0, 1)">this.setState</span>({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 1})}&gt;+&lt;/button&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> let counter = new Counter()</span>
ReactDOM.render(&lt;Counter&gt;&lt;/Counter&gt;,document.getElementById('root'))</span></pre>
</div>
<p><span style="font-size: 18px">构造函数是唯一定义状态并且赋值的地方,当我们要改变状态的值的时候需要通过setState方法,而不是直接修改state的值,并且每次调用setState的时候会引起状态的改变和组件的更新。</span></p>
<h1><strong>1.不能直接修改state的值</strong></h1>
<div class="cnblogs_code">
<pre><span style="font-size: 18px">import React from "react"<span style="color: rgba(0, 0, 0, 1)">;
import ReactDOM from </span>"react-dom"<span style="color: rgba(0, 0, 0, 1)">;

</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
* 属性是由父组件传递过来的,不能改变
* 状态是组件内部,由自己维护,外界无法访问改变状态的唯一方式就是setState
</span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
class Counter extends React.Component{
constructor(props) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造函数是唯一给状态赋值的地方</span>
<span style="color: rgba(0, 0, 0, 1)">    super(props)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义状态的地方</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.state = {number: 0<span style="color: rgba(0, 0, 0, 1)">}
}
<span style="color: rgba(255, 0, 0, 1)">add </span></span><span style="color: rgba(255, 0, 0, 1)">= () =&gt; {
    this.state.number += 1</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">
}</span>
render(){
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当我们调用setState的时候会引起状态的改变和组件的更新</span>
    console.log('render'<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.number}&lt;/p&gt;
      &lt;button onClick={<span style="color: rgba(255, 0, 0, 1)">this.add</span>}&gt;+&lt;/button&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> let counter = new Counter()</span>
ReactDOM.render(&lt;Counter&gt;&lt;/Counter&gt;,document.getElementById('root'))</span></pre>
</div>
<p><span style="font-size: 18px">像这种直接修改state值得方法并不会生效。</span></p>
<h1><strong>&nbsp;2.state的更新可能是异步</strong></h1>
<p><span style="font-size: 18px">我们知道调用setState会触发更新操作,这个过程包括更新state,创建新的VNode,在经过diff算法对比差异,决定需要渲染那一部分,假如他是同步更新的话,每次调用都要执行一次前面的流程,这样会造成很大的性能问题,所以需要将多个setState放进一个队列里面,、然后再一个一个执行,最后再一次性更新视图,这样会提高性能。举个例子:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px">let state = {number: 0<span style="color: rgba(0, 0, 0, 1)">}
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> setState(newState) {
    state </span>=<span style="color: rgba(0, 0, 0, 1)"> newState
    <span style="color: rgba(255, 0, 0, 1)">console.log(state)</span>
}
setState({number: state.number </span>+ 1<span style="color: rgba(0, 0, 0, 1)">})
setState({number: state.number </span>+ 2<span style="color: rgba(0, 0, 0, 1)">})
setState({number: state.number </span>+ 3})</span></pre>
</div>
<p><span style="font-size: 18px">这段代码会通过&nbsp;setState 方法改变state值,我们看看打印结果:</span></p>
<p><img src="https://img2020.cnblogs.com/blog/1843694/202004/1843694-20200411103602248-1736531204.png"></p>
<p>&nbsp;</p>
<p>&nbsp;<span style="font-size: 18px">可以看到每次调用setState都会改变state的值并且进行渲染,这将是一个非常消耗性能的问题。</span></p>
<p><span style="font-size: 18px">所以React针对setState做了一些特别的优化:将多个setState的调用放进了一个队列,合并成一个来执行,这意味着当调用setState时,state并不会立即更新,看下面这个例子:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px">let state = {number: 0<span style="color: rgba(0, 0, 0, 1)"><span style="font-size: 18px">}</span>
let updataQueue </span>=<span style="color: rgba(0, 0, 0, 1)"> []
</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> setState(newState) {
    updataQueue.push(newState)
}
setState({number: state.number </span>+ 1<span style="color: rgba(0, 0, 0, 1)">})
setState({number: state.number </span>+ 2<span style="color: rgba(0, 0, 0, 1)">})
setState({number: state.number </span>+ 3<span style="color: rgba(0, 0, 0, 1)">})

updataQueue.forEach(item </span>=&gt;<span style="color: rgba(0, 0, 0, 1)">{
    state </span>=<span style="color: rgba(0, 0, 0, 1)"> item
})
console.log(state) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 3</span></span></pre>
</div>
<p><span style="font-size: 18px">我们预想的是结果等于6.但是输出的却是3,这是因为变成异步更新之后state的值并不会立即更新,所以每次拿到的state都是 0 ,如果我们想要让结果等于 6,也就是每次都能拿到最新值,那就需要给setState()传递一个函数作为参数,在这个函数中可以拿到每次改变后的值,并通过这个函数的返回值得到下一个状态。</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px">let state = { number: 0<span style="color: rgba(0, 0, 0, 1)"> }</span>
let updataQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新函数队列</span>
let callbackQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">回调函数队列</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> setState(updataState,callback) {
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">入队</span>
<span style="color: rgba(0, 0, 0, 1)">    updataQueue.push(updataState)
    callbackQueue.push(callback)
   
}
</span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">清空队列</span>
<span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> flushUpdata () {
    </span><span style="color: rgba(255, 0, 0, 1)">for(let i = 0; i &lt; updataQueue.length; i++) {
      state = updataQueue(state) //拿到每次改变后的值作为下一个的状态
    }
    state = state
    callbackQueue.forEach(callbackItem =&gt;</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"> callbackItem())</span>
}

</span><span style="color: rgba(0, 0, 255, 1)">function</span><span style="color: rgba(0, 0, 0, 1)"> add(){
    setState(preState </span>=&gt; ({ number: preState.number + 1}),() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      console.log(state)
   })
   setState(preState </span>=&gt; ({ number: preState.number + 2}),() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
       console.log(state)
   })
   setState(preState </span>=&gt; ({ number: preState.number + 3}),() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
       console.log(state)
   })
   </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">批量更新</span>
<span style="color: rgba(0, 0, 0, 1)">   flushUpdata()
}

add()
console.log(state) </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> 6</span></span></pre>
</div>
<p>&nbsp;<img src="https://img2020.cnblogs.com/blog/1843694/202004/1843694-20200411174523538-486832172.png"></p>
<p>&nbsp;</p>
<p>&nbsp;<span style="font-size: 18px">由于回调函数也是异步执行的,所以最后一次性输出的都是6.</span></p>
<p><span style="font-size: 18px">&nbsp;改写成class类的形式如下:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Component {
    constructor() {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> {
            number: </span>0<span style="color: rgba(0, 0, 0, 1)">
      }
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">false</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新队列</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.callbackQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">回调函数队列</span>
<span style="color: rgba(0, 0, 0, 1)">    }
    setState(updataState, callback) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.batchUpdata) {
            </span><span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue.push(updataState) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">放入队列</span>
            <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.callbackQueue.push(callback)
      }
    }
    flushUpdata() {
      let state </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.updataQueue.forEach(newStateitem =&gt; this.state = newStateitem)</span>
      <span style="color: rgba(0, 0, 255, 1)">for</span>(let i = 0; i &lt; <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
            state </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.updataQueue(state)
      }
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> state
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.callbackQueue.forEach(callback =&gt;<span style="color: rgba(0, 0, 0, 1)"> callback())
    }
    add() {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">true</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">开启合并模式</span>

      <span style="color: rgba(0, 0, 255, 1)">this</span>.setState(preState =&gt; ({ number: preState.number + 1}),() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state)
       })
       </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState(preState =&gt; ({ number: preState.number + 2}),() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
         console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state)
       })
       </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState(preState =&gt; ({ number: preState.number + 3}),() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
         console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state)
       })

      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">批量更新</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.flushUpdata()
    }
}
let c </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Component()
c.add()
console.log(c.state)</span></span></pre>
</div>
<p><span style="font-size: 18px">现在这个逻辑对于setState传入的参数是函数很适合,但是有时候我们希望传入的是对象,且希望利用setState执行完之后做一些操作,比如在请求到数据之后隐藏进度条等,这个时候就需要setState能变为同步执行,这个时候我们会借助promise、setTimeout等方法来改变setState让它变为同步的。也就是不用放入队列,而是立即执行,但是以上逻辑不支持同步的情况,我们需要修改:</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Component {
    constructor() {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> {
            number: </span>0<span style="color: rgba(0, 0, 0, 1)">
      }
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">false</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新队列</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.callbackQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">回调函数队列</span>
<span style="color: rgba(0, 0, 0, 1)">    }
    setState(updataState, callback) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">批量更新</span>
            <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue.push(updataState) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">放入队列</span>
            <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.callbackQueue.push(callback)
      }</span><span style="color: rgba(255, 0, 0, 1)">else {</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">直接更新</span>
            console.log('直接更新'<span style="color: rgba(0, 0, 0, 1)">)
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果是函数需要把老值传进去</span>
            <span style="color: rgba(255, 0, 0, 1)">if(typeof updataState === 'function') {
                this.state = updataState(this.state)
            }else {
                this.state =</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"> updataState
            }</span>
      }
    }
    flushUpdata() {
      let state </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> console.log(this.updataQueue)</span>
      <span style="color: rgba(0, 0, 255, 1)">for</span>(let i = 0; i &lt; <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">为了兼容参数为函数和对象的情况需要判断一下 参数为对象的时候不用传上一个的状态值,参数为函数的时候需要传上一个的状态给下一个状态</span>
            <span style="color: rgba(255, 0, 0, 1)">if(typeof this.updataQueue === 'function') {
                state = this.updataQueue(state)
            }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                state </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.updataQueue
            }
      }
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> state
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.callbackQueue.forEach(callback =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(255, 0, 0, 1)">if(callback) callback()</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">为了兼容参数为函数和对象的情况需要判断一下,参数为对象的时候没有回调函数就不执行</span>
<span style="color: rgba(0, 0, 0, 1)">      })
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">false</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新完毕置为false</span>
<span style="color: rgba(0, 0, 0, 1)">    }
    add() {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">true</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">开启合并模式</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">不会放进更新队列</span>
       <span style="color: rgba(255, 0, 0, 1)">setTimeout(() =&gt; {
      this.setState({number: this.state.number + 4})
      console.log(this.state)
       },1000)
      this.setState({number: this.state.number + 1})
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">   this.setState(preState =&gt; ({ number: preState.number + 1}),() =&gt; {</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">         console.log(this.state)</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">    })</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">    this.setState({number: this.state.number + 1})</span>

      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">批量更新</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.flushUpdata()
    }
}
let c </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Component()
c.add()
console.log(</span>'end'+ JSON.stringify(c.state))</span></pre>
</div>
<p><img src="https://img2020.cnblogs.com/blog/1843694/202004/1843694-20200411184756278-548509814.png"></p>
<p>&nbsp;</p>
<p><span style="font-size: 18px">批量处理机制就是为了减少setState刷新页面的次数,setTimeout,promise等异步方法可以直接跳过批量处理机制,setState调几次就改几次。</span></p>
<p><span style="font-size: 18px">&nbsp;https://www.cnblogs.com/jiuyi/p/9263114.html这篇文章对于同步更新讲的比较好</span></p>
<h1>3.seState的更新会被合并</h1>
<p><span style="font-size: 18px">当调用setState的时候,React会把你要修改的那一部分的对象合并到当前的state上面,举个栗子</span>:</p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Counter extends React.Component{
constructor(props) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造函数是唯一给状态赋值的地方</span>
<span style="color: rgba(0, 0, 0, 1)">    super(props)
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.add = <span style="color: rgba(0, 0, 255, 1)">this</span>.add.bind(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义状态的地方</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.state = {name: 'leah' ,number: 0<span style="color: rgba(0, 0, 0, 1)">}
}
</span><span style="color: rgba(0, 0, 0, 1)">
add (event) {
    console.log(event)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.state.number += 1 不能直接修改state的值</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 1<span style="color: rgba(0, 0, 0, 1)">})
}
render(){
    console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当我们调用setState的时候会引起状态的改变和组件的更新</span>
    console.log('render'<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.name}&lt;/p&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.number}&lt;/p&gt;
      &lt;button onClick={<span style="color: rgba(0, 0, 255, 1)">this</span>.add}&gt;+&lt;/button&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}</span></span></pre>
</div>
<p><span style="font-size: 18px">当前我们只修改了state.number这个时候,name还是会渲染,我们需要对这部分进行合并</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Component {
    constructor() {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> {
            name: </span>'leah'<span style="color: rgba(0, 0, 0, 1)">,
            number: </span>0<span style="color: rgba(0, 0, 0, 1)">
      }
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">false</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新队列</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span>.callbackQueue = [] <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">回调函数队列</span>
<span style="color: rgba(0, 0, 0, 1)">    }
    setState(updataState, callback) {
      </span><span style="color: rgba(0, 0, 255, 1)">if</span> (<span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata) { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">批量更新</span>
            <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue.push(updataState) <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">放入队列</span>
            <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.callbackQueue.push(callback)
      }</span><span style="color: rgba(0, 0, 255, 1)">else</span> { <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">直接更新</span>
            console.log('直接更新'<span style="color: rgba(0, 0, 0, 1)">)
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">如果是函数需要把老值传进去</span>
            <span style="color: rgba(0, 0, 255, 1)">if</span>(<span style="color: rgba(0, 0, 255, 1)">typeof</span> updataState === 'function'<span style="color: rgba(0, 0, 0, 1)">) {
                </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state = updataState(<span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state)
            }</span><span style="color: rgba(0, 0, 255, 1)">else</span><span style="color: rgba(0, 0, 0, 1)"> {
                </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> updataState
            }
      }
    }
    flushUpdata() {
      let state </span>= <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state
      </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> console.log(this.updataQueue)</span>
      <span style="color: rgba(0, 0, 255, 1)">for</span>(let i = 0; i &lt; <span style="color: rgba(0, 0, 255, 1)">this</span>.updataQueue.length; i++<span style="color: rgba(0, 0, 0, 1)">) {
            </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">为了兼容参数为函数和对象的情况需要判断一下 参数为对象的时候不用传上一个的状态值,参数为函数的时候需要传上一个的状态给下一个状态</span>
            <span style="color: rgba(255, 0, 0, 1)">let partialState = typeof this.updataQueue === 'function' ? this.updataQueue(this.state) : this.updataQueue
            state =</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)"> {...state, ...partialState}</span>
      }
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.state =<span style="color: rgba(0, 0, 0, 1)"> state
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.callbackQueue.forEach(callback =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
            </span><span style="color: rgba(0, 0, 255, 1)">if</span>(callback) callback() <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">为了兼容参数为函数和对象的情况需要判断一下,参数为对象的时候没有回调函数就不执行</span>
<span style="color: rgba(0, 0, 0, 1)">      })
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">false</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">更新完毕置为false</span>
<span style="color: rgba(0, 0, 0, 1)">    }
    add() {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.batchUpdata = <span style="color: rgba(0, 0, 255, 1)">true</span> <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">开启合并模式</span>
      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">不会放进更新队列</span>
       setTimeout(() =&gt;<span style="color: rgba(0, 0, 0, 1)"> {
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 4<span style="color: rgba(0, 0, 0, 1)">})
      console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.state)
       },</span>1000<span style="color: rgba(0, 0, 0, 1)">)
      </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 1<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">   this.setState(preState =&gt; ({ number: preState.number + 1}),() =&gt; {</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">         console.log(this.state)</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">    })</span>
    <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">    this.setState({number: this.state.number + 1})</span>

      <span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">批量更新</span>
      <span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">.flushUpdata()
    }
}
let c </span>= <span style="color: rgba(0, 0, 255, 1)">new</span><span style="color: rgba(0, 0, 0, 1)"> Component()
c.add()
console.log(</span>'end'+ JSON.stringify(c.state))</span></pre>
</div>
<p><strong style="font-size: 2em">4.在组件实例中this的指向问题:</strong></p>
<p><span style="font-size: 18px">一般来说,类的方法里this是undefined,那如何让普通方法的this指向组件实例呢?</span></p>
<p><span style="font-size: 18px"><strong>4.1.箭头函数</strong></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Counter extends React.Component{
constructor(props) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造函数是唯一给状态赋值的地方</span>
<span style="color: rgba(0, 0, 0, 1)">    super(props)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义状态的地方</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.state = {number: 0<span style="color: rgba(0, 0, 0, 1)">}
}</span><span style="color: rgba(255, 0, 0, 1)">
add = (event) =&gt;</span><span style="color: rgba(0, 0, 0, 1)"> {
    console.log(event)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.state.number += 1 不能直接修改state的值</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 1<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 2<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 3<span style="color: rgba(0, 0, 0, 1)">})
}
render(){
    console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当我们调用setState的时候会引起状态的改变和组件的更新</span>
    console.log('render'<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.number}&lt;/p&gt;
      &lt;button onClick={<span style="color: rgba(0, 0, 255, 1)">this</span>.add}&gt;+&lt;/button&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}</span></span></pre>
</div>
<p><span style="font-size: 18px"><strong>4.2.匿名函数</strong></span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Counter extends React.Component{
constructor(props) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造函数是唯一给状态赋值的地方</span>
<span style="color: rgba(0, 0, 0, 1)">    super(props)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义状态的地方</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.state = {number: 0<span style="color: rgba(0, 0, 0, 1)">}
}</span><span style="color: rgba(0, 0, 0, 1)"><span style="color: rgba(255, 0, 0, 1)">
add (event)</span> {
    console.log(event)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.state.number += 1 不能直接修改state的值</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 1<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 2<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 3<span style="color: rgba(0, 0, 0, 1)">})
}
render(){
    console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当我们调用setState的时候会引起状态的改变和组件的更新</span>
    console.log('render'<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.number}&lt;/p&gt;
      &lt;button onClick={<span style="color: rgba(255, 0, 0, 1)">() =&gt; this.add()</span>}&gt;+&lt;/button&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}</span></span></pre>
</div>
<p><strong><span style="font-size: 18px">4.3.bind绑定</span></strong></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Counter extends React.Component{
constructor(props) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造函数是唯一给状态赋值的地方</span>
<span style="color: rgba(0, 0, 0, 1)">    super(props)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义状态的地方</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.state = {number: 0<span style="color: rgba(0, 0, 0, 1)">}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
   * 合成事件 react合成事件
   * 事件代理
   * event 并不是原始的dom对象 而是react二次封装的事件对象 可以复用
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
add (event) {
    console.log(event)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.state.number += 1 不能直接修改state的值</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 1<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 2<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 3<span style="color: rgba(0, 0, 0, 1)">})
}
render(){
    console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当我们调用setState的时候会引起状态的改变和组件的更新</span>
    console.log('render'<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.number}&lt;/p&gt;
      &lt;button onClick={<span style="color: rgba(255, 0, 0, 1)">this.add.bind(this)</span>}&gt;+&lt;/button&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}</span></span></pre>
</div>
<p><span style="font-size: 18px">但是这样绑定有个问题,就是每次渲染的时候都需要绑定一次,所以可以在构造函数里面一次性绑定</span></p>
<div class="cnblogs_code">
<pre><span style="font-size: 18px"><span style="color: rgba(0, 0, 0, 1)">class Counter extends React.Component{
constructor(props) { </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">构造函数是唯一给状态赋值的地方</span>
<span style="color: rgba(0, 0, 0, 1)">    super(props)
    </span><span style="color: rgba(255, 0, 0, 1)">this.add = this.add.bind(this)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">定义状态的地方</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.state = {number: 0<span style="color: rgba(0, 0, 0, 1)">}
}
</span><span style="color: rgba(0, 128, 0, 1)">/*</span><span style="color: rgba(0, 128, 0, 1)">*
   * 合成事件 react合成事件
   * 事件代理
   * event 并不是原始的dom对象 而是react二次封装的事件对象 可以复用
   </span><span style="color: rgba(0, 128, 0, 1)">*/</span><span style="color: rgba(0, 0, 0, 1)">
add (event) {
    console.log(event)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)"> this.state.number += 1 不能直接修改state的值</span>
    <span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 1<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 2<span style="color: rgba(0, 0, 0, 1)">})
    </span><span style="color: rgba(0, 0, 255, 1)">this</span>.setState({number: <span style="color: rgba(0, 0, 255, 1)">this</span>.state.number + 3<span style="color: rgba(0, 0, 0, 1)">})
}
render(){
    console.log(</span><span style="color: rgba(0, 0, 255, 1)">this</span><span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 128, 0, 1)">//</span><span style="color: rgba(0, 128, 0, 1)">当我们调用setState的时候会引起状态的改变和组件的更新</span>
    console.log('render'<span style="color: rgba(0, 0, 0, 1)">)
    </span><span style="color: rgba(0, 0, 255, 1)">return</span><span style="color: rgba(0, 0, 0, 1)"> (
      </span>&lt;div&gt;
      &lt;p&gt;{<span style="color: rgba(0, 0, 255, 1)">this</span>.state.number}&lt;/p&gt;
      &lt;button onClick={<span style="color: rgba(255, 0, 0, 1)">this.add</span>}&gt;+&lt;/button&gt;
      &lt;/div&gt;
<span style="color: rgba(0, 0, 0, 1)">    )
}
}</span></span></pre>
</div>
<h1><strong>5.合成事件</strong></h1>
<p><span style="font-size: 18px">合成事件原理:利用事件冒泡机制</span><br><span style="font-size: 18px">如果react事件绑定在了真实DOM节点上,一个节点同事有多个事件时,页面的响应和内存的占用会受到很大的影响。因此SyntheticEvent作为中间层出现了。</span><br><span style="font-size: 18px">事件没有在目标对象上绑定,而是在document上监听所支持的所有事件,当事件发生并冒泡至document时,react将事件内容封装并叫由真正的处理函数运行。</span></p>
<p><span style="font-size: 18px">&nbsp;</span></p>
<p><span style="font-size: 18px">这篇主要讲了一下setState的异步更新处理过程。</span></p>
<p><span style="font-size: 18px">我们的更新其实并不是真正的异步处理,而是更新的时候把更新内容放到了更新队列中,最后批次更新,这样才表现出异步更新的状态。setTimeout,promise等异步方法可以直接跳过批量处理机制,setState调几次就改几次。</span></p>

</div>
<div id="MySignature" role="contentinfo">
    不积跬步无以至千里<br><br>
来源:https://www.cnblogs.com/lyt0207/p/12677651.html
頁: [1]
查看完整版本: react setState 原理