如下幾個問題是咱們在實際開發中常常會遇到的場景,下面用幾個簡單的示例代碼來還原一下。javascript
setState
如今有兩個組件java
componentDidMount() {
console.log('parent componentDidMount');
}
render() {
return (
<div> <SetState2></SetState2> <SetState></SetState> </div>
);
}
複製代碼
組件內部放入一樣的代碼,並在Setstate1
中的componentDidMount
中放入一段同步延時代碼,打印延時時間:react
componentWillUpdate() {
console.log('componentWillUpdate');
}
componentDidUpdate() {
console.log('componentDidUpdate');
}
componentDidMount() {
console.log('SetState調用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
console.log('SetState調用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
}
複製代碼
下面是執行結果:git
說明:github
setState
不會當即更新didmount
後,父組件didmount
,而後執行更新setstate
?在setTimeout
中調用setState
(例子和在瀏覽器原生事件以及接口回調中執行效果相同)瀏覽器
componentDidMount() {
setTimeout(() => {
console.log('調用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
console.log('調用setState');
this.setState({
index: this.state.index + 1
})
console.log('state', this.state.index);
}, 0);
}
複製代碼
執行結果:bash
說明:dom
didmount
後執行setState
同步更新setState
只有一次生效?分別執行如下代碼:異步
componentDidMount() {
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
this.setState({ index: this.state.index + 1 }, () => {
console.log(this.state.index);
})
}
複製代碼
componentDidMount() {
this.setState((preState) => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
})
this.setState(preState => ({ index: preState.index + 1 }), () => {
console.log(this.state.index);
})
}
複製代碼
執行結果:函數
1
1
複製代碼
2
2
複製代碼
說明:
setstate
會被合併成一次state
不會被合併因爲源碼比較複雜,就不貼在這裏了,有興趣的能夠去github
上clone
一份而後按照下面的流程圖去走一遍。
partialState
:setState
傳入的第一個參數,對象或函數_pendingStateQueue
:當前組件等待執行更新的state
隊列isBatchingUpdates
:react用於標識當前是否處於批量更新狀態,全部組件公用dirtyComponent
:當前全部處於待更新狀態的組件隊列transcation
:react的事務機制,在被事務調用的方法外包裝n個waper
對象,並一次執行:waper.init
、被調用方法、waper.close
FLUSH_BATCHED_UPDATES
:用於執行更新的waper
,只有一個close
方法對照上面流程圖的文字說明,大概可分爲如下幾步:
partialState
參數存儲在當前組件實例的state暫存隊列中。waper
方法,遍歷待更新組件隊列依次執行更新。componentWillReceiveProps
。state
進行合併,得到最終要更新的state對象,並將隊列置爲空。componentShouldUpdate
,根據返回值判斷是否要繼續更新。componentWillUpdate
。render
。componentDidUpdate
。在react
的生命週期和合成事件中,react
仍然處於他的更新機制中,這時isBranchUpdate
爲true。
按照上述過程,這時不管調用多少次setState
,都會不會執行更新,而是將要更新的state
存入_pendingStateQueue
,將要更新的組件存入dirtyComponent
。
當上一次更新機制執行完畢,以生命週期爲例,全部組件,即最頂層組件didmount
後會將isBranchUpdate
設置爲false。這時將執行以前累積的setState
。
由執行機制看,setState
自己並非異步的,而是若是在調用setState
時,若是react
正處於更新過程,當前更新會被暫存,等上一次更新執行後在執行,這個過程給人一種異步的假象。
在生命週期,根據JS的異步機制,會將異步函數先暫存,等全部同步代碼執行完畢後在執行,這時上一次更新過程已經執行完畢,isBranchUpdate
被設置爲false,根據上面的流程,這時再調用setState
便可當即執行更新,拿到更新結果。
partialState
合併機制咱們看下流程中_processPendingState
的代碼,這個函數是用來合併state
暫存隊列的,最後返回一個合併後的state
。
_processPendingState: function (props, context) {
var inst = this._instance;
var queue = this._pendingStateQueue;
var replace = this._pendingReplaceState;
this._pendingReplaceState = false;
this._pendingStateQueue = null;
if (!queue) {
return inst.state;
}
if (replace && queue.length === 1) {
return queue[0];
}
var nextState = _assign({}, replace ? queue[0] : inst.state);
for (var i = replace ? 1 : 0; i < queue.length; i++) {
var partial = queue[i];
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
}
return nextState;
},
複製代碼
咱們只須要關注下面這段代碼:
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
複製代碼
若是傳入的是對象,很明顯會被合併成一次:
Object.assign(
nextState,
{index: state.index+ 1},
{index: state.index+ 1}
)
複製代碼
若是傳入的是函數,函數的參數preState是前一次合併後的結果,因此計算結果是準確的。
componentDidMount
調用setstate
在componentDidMount()中,你 能夠當即調用setState()。它將會觸發一次額外的渲染,可是它將在瀏覽器刷新屏幕以前發生。這保證了在此狀況下即便render()將會調用兩次,用戶也不會看到中間狀態。謹慎使用這一模式,由於它常致使性能問題。在大多數狀況下,你能夠 在constructor()中使用賦值初始狀態來代替。然而,有些狀況下必須這樣,好比像模態框和工具提示框。這時,你須要先測量這些DOM節點,才能渲染依賴尺寸或者位置的某些東西。
以上是官方文檔的說明,不推薦直接在componentDidMount
直接調用setState
,由上面的分析:componentDidMount
自己處於一次更新中,咱們又調用了一次setState
,就會在將來再進行一次render
,形成沒必要要的性能浪費,大多數狀況能夠設置初始值來搞定。
固然在componentDidMount
咱們能夠調用接口,再回調中去修改state
,這是正確的作法。
當state初始值依賴dom屬性時,在componentDidMount
中setState
是沒法避免的。
componentWillUpdate
componentDidUpdate
這兩個生命週期中不能調用setState
。
由上面的流程圖很容易發現,在它們裏面調用setState
會形成死循環,致使程序崩潰。
在調用setState
時使用函數傳遞state
值,在回調函數中獲取最新更新後的state
。