概述
setState中對於某個state多次修改,只執行一次(最后一次),所以可以將修改放在同一次中
import React, {Component} from 'react';
class Demotest extends Component {
constructor(props) {
super(props);
this.state = {
number: 1
};
}
componentDidMount() {
this.setState({ number: this.state.number + 1 });
}
addNumber(e) {
this.setState({ number: this.state.number + 1 });
this.setState({ number: this.state.number + 1 });
this.setState({ number: this.state.number + 1 });
console.log(this.state.number);
}
render() {
return (
<div>
<div>
<span>當前數字是{this.state.number}</span>
</div>
<br/>
<div>
<button onClick={e => {
this.addNumber(e);
}}>點擊添加
</button>
</div>
</div>
);
}
}
export default Demotest;
初始加載后
這時發現頁面上顯示的是2,控制台輸出的卻是1,按道理 componentDidMount
里的應該已經成功了,不然不會顯示2,那為什么控制台輸出的卻是1 呢?
由於 setState
是異步的所以,所以同步代碼執行結束后才會執行,所以在 console.log('componentDidMount: ', this.state.number);
執行的時候 state
還沒有被改變,所以生命周期里的輸出還是原來的值。
此時我們點擊按鈕,觸發函數 addNumber
發現,函數里的三次 setState
只生效了一次 ,頁面顯示的數字變成了3,控制台輸出了**2 **(對應上面代碼20行),這是因為多次更新被合並,而異步的原因導致只輸出了2。官方文檔上明確說明,如果希望通過這里的狀態更新一下個狀態,需要在 setState
中使用函數來取得
addNumber(e) {
this.setState((nextState) => {
console.log(nextState.number);
return { number: nextState.number + 1 };
});
this.setState((nextState) => {
console.log(nextState.number);
return { number: nextState.number + 1 };
});
this.setState((nextState) => {
console.log(nextState.number);
return { number: nextState.number + 1 };
});
}
此時輸出的是2、3、4
這里要說明的是當你在 ****willMount**
前設進行 state
的設置不會 render
的觸發,而事件和可以 componentDidMount
觸發 render
, 而且原生的事件可以優先這個機制
componentDidMount() {
document.body.addEventListener('click', this.updateData, false);
}
updateData = () => {
this.setState({
number: this.state.number + 1
});
console.log('componentDidMount: ', this.state.number);
};
詳解setState
上面概述了下setState會出現的'合並',下面引用官網的一段話
setState()
將對組件 state 的更改排入隊列,並通知 React 需要使用更新后的 state 重新渲染此組件及其子組件。這是用於更新用戶界面以響應事件處理器和處理服務器數據的主要方式
將setState()
視為_請求_而不是立即更新組件的命令。為了更好的感知性能,React 會延遲調用它,然后通過一次傳遞更新多個組件。React 並不會保證 state 的變更會立即生效。
setState()
並不總是立即更新組件。它會批量推遲更新。這使得在調用setState()
后立即讀取this.state
成為了隱患。為了消除隱患,請使用componentDidUpdate
或者setState
的回調函數(setState(updater, callback)
),這兩種方式都可以保證在應用更新后觸發。如需基於之前的 state 來設置當前的 state,請閱讀下述關於參數updater
的內容。
除非shouldComponentUpdate()
返回false
,否則setState()
將始終執行重新渲染操作。如果可變對象被使用,且無法在shouldComponentUpdate()
中實現條件渲染,那么僅在新舊狀態不一時調用setState()
可以避免不必要的重新渲染
下面通過幾個例子分析setState在實際應用中的運用和注意點
附上一段源碼
Component.prototype.setState = function(partialState, callback) {
// 校驗是否符合三種情況
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
// 接受狀態變量的對象以進行更新或者通過函數返回狀態變量的對象
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
enqueueSetState: function (publicInstance, partialState) {
if (process.env.NODE_ENV !== 'production') {
ReactInstrumentation.debugTool.onSetState();
process.env.NODE_ENV !== 'production' ? warning(partialState != null, 'setState(...): You passed an undefined or null state object; ' + 'instead, use forceUpdate().') : void 0;
}
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState');
if (!internalInstance) {
return;
}
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []);
queue.push(partialState);
enqueueUpdate(internalInstance);
}
function enqueueUpdate(internalInstance) {
ReactUpdates.enqueueUpdate(internalInstance);
}
function enqueueUpdate(component) {
ensureInjected();
// Various parts of our code (such as ReactCompositeComponent's
// _renderValidatedComponent) assume that calls to render aren't nested;
// verify that that's the case. (This is called by each top-level update
// function, like setState, forceUpdate, etc.; creation and
// destruction of top-level components is guarded in ReactMount.)
// 是批處理更新, 默認為false
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component);
return;
}
dirtyComponents.push(component);
if (component._updateBatchNumber == null) {
component._updateBatchNumber = updateBatchNumber + 1;
}
}
setState的第一個參數
// 官網的api ---> 第一個參數可以是對象或者一個函數
setState(updater, [callback])
如果傳入的是對象則會淺層合並到新的 state 中,后調用的 setState()
將覆蓋同一周期內先調用 setState
的值
Object.assign(
previousState,
{quantity: state.quantity + 1},
{quantity: state.quantity + 1},
...
)
相當於同個屬性被合並。
如果第一個參數是函數,形式如下
(state, props) => stateChange
函數中接收的 state
和 props
都保證為最新, 是你的上次setState的狀態值。
具體例子
import React, { Component } from 'react';
class App extends Component {
constructor(props) {
super(props)
this.state = {
number: 0
}
}
// 運行一遍,不理解的對照注釋看
add () {
// 進入隊列 number: 0 + 222 = 222
this.setState({
number: this.state.number + 222
})
// 進入隊列 number: 0 + 5 =5
this.setState({
number: this.state.number + 5
})
// 進入隊列 number: 5 + 1 = 6
this.setState((state, props) => {
// 此時state是上次setState中的number ==> 5
console.log('one', state);
return {
number: state.number + 1
}
})
// 進入隊列 number: 0 + 1 = 1
this.setState({
number: this.state.number + 1
})
// 進入隊列: 1 + 2 = 3
this.setState((state, props) => {
// 此時state是上次setState中的number ==> 1
console.log('two', state);
return {
number: state.number + 2
}
})
// 進入隊列: 3 + 1 = 4
this.setState((state, props) => {
// 此時state是上次setState中的number ==> 3
console.log('three', state);
return {
number: state.number + 1
}
})
}
render () {
return (
<div>
<button onClick={e =>{this.add()}}>add</button>
{this.state.number}
</div>
);
}
}
export default App;
輸出分別為 5 、 1 、 3,最后頁面顯示的是4。
第二個參數
setState()
的第二個參數為可選的回調函數,它將在setState
完成合並並重新渲染組件后執行。通常,我們建議使用componentDidUpdate()
來代替此方式。
考慮如下場景:
在同個組件中一個具有進度條的頁面需要不停的加載請求,只有當某次請求正確返回數據后出現新的內容區域。當數據返回后你將進度條比例設置為100,同時設置數據到state上。
this.ajax() {
this.$post(xxx.com)
.then(res => {
this.setState({
progress: 100,
data: res
})
})
.catch(err => {console.log(err)})
}
設置為100%是為了數據到來后進度條立馬拉滿,然后在渲染對應的數據內容區域,這樣寫會出現進度條其實也拉滿了,但是視覺效果沒出來數據內容區域就出來的情況。所以用到第二個參數
this.ajax() {
this.$post(xxx.com)
.then(res => {
this.setState({
progress: 100,
}, () => {
this.setState({
data: res
})
})
})
.catch(err => {console.log(err)})
}