setState作為react中的重要部分,將對元件 state 的變更排入佇列,並通知 React 需要使用更新後的 state 重新渲染此元件及其子元件。以下這篇文章帶大家了解React中的setState機制,希望對大家有幫助!
state
是React
中的重要概念。我們知道,React
是透過狀態管理來實現對元件的管理。那麼,React
是如何控制元件的狀態的,又是如何利用狀態來管理元件的呢? 【相關推薦:Redis影片教學】
我們都知道,React
透過this.state
來存取state
,透過this.setState()
方法更新state
。當this.setState()
被呼叫的時候,React
會重新呼叫render
方法來重新渲染UI
。
setState
已經是我們非常熟悉的一個API
,然而你真的了解它嗎?下面我們將一起來解密setState
的更新機制。
大家剛開始寫React
的時候,通常會寫出this.state.value = 1
這樣的程式碼,這是完全錯誤的寫法。
注意:絕對不要直接修改 this.state,這不僅是一種低效的做法,而且很有可能會被之後的操作替換。
setState
透過一個佇列機制實作state
更新。當執行setState
時,會將需要更新的state
合併後放入狀態對列,而不會立刻更新this.state
,佇列機制可以高效地批量更新state
。如果不透過setState
而直接修改this.state
的值,那麼該state
將不會被放入狀態佇列中,當下次呼叫 setState
並對狀態佇列進行合併時,將會忽略先前直接被修改的 state
,而造成無法預測的錯誤。
因此,應該使用setState
方法來更新state
,同時React
也正是利用狀態佇列機制實現了setState
的非同步更新,避免頻繁地重複更新state
。相關程式碼如下:
// 将新的state合并到状态更新队列中 var nextState = this._processPendingState(nextProps, nextContext); // 根据更新队列和 shouldComponentUpdate 的状态来判断是否需要更新组件 var shouldUpdate = this._pendingForceUpdte || !inst.shouldCompoonentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext0;
當呼叫setState
時,實際上會執行enqueueSetState
方法,並對 partialState
以及_pendingStateQueue
更新佇列進行合併操作,最終操作enqueueSetState
執行state
更新。
而performUpdateIfNecessary
方法會取得_pendingElement、_pendingStateQueue、_pendingForceUpdate
,並呼叫receiveComponent
和updateComponent
# 方法進行元件更新。
如果在shouldComponetUpdate
或componentWillUpdate
方法中呼叫setState
, 此時this._pendingStateQueue != null
, 則performUpateIfNecessary
方法就會呼叫updateComponent
方法進行元件更新,但updateComponent
方法又會呼叫shouldComponentUpdate
和方法,因此造成循環調用,使得瀏覽器記憶體佔滿後崩潰。
setState 最終是透過
enqueueUpate#state
#更新,那麼
enqueueUpdate 到底是如何更新
state 的呢?
import React, { Component } from 'react' class Example extends Component { constructor() { super() this.state = { val: 0 } } componentDidMount() { this.setState({val: this.state.val + 1}) console.log(this.state.val) this.setState({val: this.state.val + 1}) console.log(this.state.val) setTimeout(() => { this.setState({val: this.state.val + 1}) console.log(this.state.val) this.setState({val: this.state.val + 1}) console.log(this.state.val) },0) } render() { return null } }
console.log 列印出來的
val 分別是:
0、0、2、3。
enqueueUpdate 到底做了什麼?
下圖是一個簡化的
setState 呼叫棧,注意其中核心的狀態判斷。
setState簡化呼叫堆疊
setState的各種不同表現的呢?
我们先要了解事务跟 setState
的不同表现有什么关系。首先,我们把4次 setState
简单归类,前两次属于一类,因为他们在同一次调用栈中执行,setTimeout
中的两次 setState
属于另一类,因为他们也是在同一次调用栈中执行。我们分析一下这两类 setState
的调用栈。
在 componentDidMount
中直接调用的两次 setState
,其调用栈更加复杂;而setTimeout
中调用的两次 setState
,其调用栈则简单很多。下面我们重点看看第一类 setState
的调用栈,我们发现了 batchedUpdates
方法,原来早在 setState
调用前,已经处于batchedUpdates
执行的事务中了。
那batchedUpdates
方法,又是谁调用的呢?我们再往前追溯一层,原来是 ReactMount.js 中的 _renderNewRootComponent
方法。也就是说,整个将React组件渲染到DOM中的过程就处于一个大的事务中。
接下来的解释就顺理成章了,因为在componentDidMount
中调用setState
时,batchingStrategy
的 isBatchingUpdates
已经被设为true
,所以两次setState
的结果并没有立即生效,而是被放到了dirtyComponents
中。这也解释了两次打印 this.state.val
都是 0
的原因,因为新的 state
还没有被应用到组件中。
componentDidMount
中setState
的调用栈
setTimeout
中setState
的调用栈
再反观 setTimeout
中的两次setState
,因为没有前置的 batchedUpdate
调用,所以 batchingStrategy
的 isBatchingUpates
标志位是false
,也就导致了新的 state
马上生效,没有走到 dirtyComponents
分支。也就是说,setTimeout
中第一次执行 setState
时,this.state.val
为 1
, 而 setState
完成打印后打印时 this.state.val
变成了2
。第二次的 setState
同理。
前面介绍事务时,也提到了其在 React
源码中的多处应用,像 initialize、perform、close、closeAll、motifyAll
等方法出现在调用栈中,都说明当前处于一个事务中。
既然事务这么有用,我们写应用代码时能使用它吗?很可惜,答案是不能。尽管React
不建议我们直接使用事务,但在 React 15.0
之前的版本中还是为开发者提供了 batchedUpdates
方法,它可以解决针对一开始例子中setTimeout
里的两次 setState
导致两次 render
的情况:
import ReactDOM, { unstable_batchedUpates } from 'teact-dom' unstable_batchedUpates(() => { this.setState(val: this.state.val + 1) this.setState(val: this.state.val + 1) })
在 React 15.0
以及之后版本中,已经彻底将 batchUpdates
这个 API
移除了,因此不再建议开发者使用它。
在使用React
的setState
的过程中,了解setState
的实现原理,对setState
异步更新、setState
循环调用风险、setState
调用栈等进行更加全面的了解,才能让我们在遇到相关问题的时候更加游刃有余。
更多编程相关知识,请访问:编程入门!!
以上是深入了解React中setState的更新機制的詳細內容。更多資訊請關注PHP中文網其他相關文章!