updater: 更新數據 FUNCTION/OBJECT callback: 更新成功後的回調 FUNCTION
// updater - Function this.setState((prevState, props) => { return {counter: prevState.counter + props.step}; }); // update - Object this.setState({quantity: 2})
1.異步:react一般會集齊一批須要更新的組件,而後一次性更新來保證渲染的性能 2.淺合併 Objecr.assign()
// setState回調函數 changeTitle: function (event) { this.setState({ title: event.target.value }, () => this.APICallFunction()); }, APICallFunction: function () { // Call API with the updated value }
onClick = () => { this.setState({ index: this.state.index + 1 }); this.setState({ index: this.state.index + 1 }); } // 最後解析爲,後面的數據會覆蓋前面的更改,因此最終只加了一次. Object.assign( previousState, {index: state.index+ 1}, {index: state.index+ 1}, ) //正確寫法 onClick = () => { this.setState((prevState, props) => { return {quantity: prevState.quantity + 1}; }); this.setState((prevState, props) => { return {quantity: prevState.quantity + 1}; }); }
1.不要在render()函數裏面寫setstate(),除非你本身定製了shouldComponentUpdate方法,要否則會引發無限循環
render() { //this.setState return( //...dom ) }
2.爲何不能使用第一個方式更新 react爲了實現高效render, state實際上是一個隊列,setState是將數據插入隊列中,使用第一種不會觸發渲染, react提供了setState的實例方法能夠觸發render,能夠看後面的源碼
// 1 this.state.num = 1 // 2 this.setState({ num: this.state.num + 1 })
3.對數組和對象等引用對象操做時,使用返回新對象的方法 array: 不要使用push、pop、shift、unshift、splice可以使用concat、slice、filter、擴展語法 object: Object.assgin/擴展語法
如圖: pending queue 和 update queuereact
ReactComponent.prototype.setState = function (partialState, callback) { // 將setState事務放進隊列中 // this.updater就是ReactUpdateQueue, this是組件的實例 this.updater.enqueueSetState(this, partialState); if (callback) { this.updater.enqueueCallback(this, callback, 'setState'); } };
enqueueSetState: function (publicInstance, partialState) { // 獲取當前組件的instance // 實例中有兩個很是重要的屬性:_pendingStateQueue(待更新隊列) 與 _pendingCallbacks(更新回調隊列) var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState'); // 初始化待更新隊列 var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = []); // 將要更新的state放入一個數組裏 queue.push(partialState); // 將要更新的component instance也放在一個隊列裏 enqueueUpdate(internalInstance); }
當前若是正處於建立/更新組件的過程,就不會馬上去更新組件,而是先把當前的組件放在dirtyComponent裏,因此不是每一次的setState都會更新組件
function enqueueUpdate(component) { // 若是沒有處於批量建立/更新組件的階段,則處理update state事務 if (!batchingStrategy.isBatchingUpdates) { batchingStrategy.batchedUpdates(enqueueUpdate, component); return; } // 若是正處於批量建立/更新組件的過程,將當前的組件放在dirtyComponents數組中 dirtyComponents.push(component); }
var ReactDefaultBatchingStrategy = { // 用於標記當前是否出於批量更新 isBatchingUpdates: false, // 當調用這個方法時,正式開始批量更新 batchedUpdates: function (callback, a, b, c, d, e) { var alreadyBatchingUpdates = ReactDefaultBatchingStrategy.isBatchingUpdates; ReactDefaultBatchingStrategy.isBatchingUpdates = true; // 若是當前事務正在更新過程在中,則調用callback,既enqueueUpdate if (alreadyBatchingUpdates) { return callback(a, b, c, d, e); } else { // 不然執行更新事務 return transaction.perform(callback, null, a, b, c, d, e); } } };
initalize(空函數) -> perform(anyMethos) -> close
// 將isBatchingUpdates置爲false var RESET_BATCHED_UPDATES = { initialize: emptyFunction, close: function () { ReactDefaultBatchingStrategy.isBatchingUpdates = false; } }; // 循環全部dirtyComponent,調用updateComponent來執行全部的生命週期方法,componentWillReceiveProps, shouldComponentUpdate, componentWillUpdate, render, componentDidUpdate 最後實現組件的更新 var FLUSH_BATCHED_UPDATES = { initialize: emptyFunction, close: ReactUpdates.flushBatchedUpdates.bind(ReactUpdates) }; var TRANSACTION_WRAPPERS = [FLUSH_BATCHED_UPDATES, RESET_BATCHED_UPDATES];
面試官:「react中setState是同步的仍是異步?」 我:「異步的,setState不能立馬拿到結果。」 面試官:「那什麼場景下是異步的,可不多是同步,什麼場景下又是同步的?」
下題結果是:面試
class App extends React.Component { 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 <div>{this.state.val}</div> } } // 結果就爲 0, 0, 2, 3
1.setState 只在合成事件和鉤子函數中是「異步」的,在原生事件和 setTimeout 中都是同步的。 2.setState的「異步」並非說內部由異步代碼實現,其實自己執行的過程和代碼都是同步的,只是合成事件和鉤子函數的調用順序在更新以前,致使在合成事件和鉤子函數中無法立馬拿到更新後的值,形式了所謂的「異步」,固然能夠經過第二個參數 setState(partialState, callback) 中的callback拿到更新後的結果。 3.setState 的批量更新優化也是創建在「異步」(合成事件、鉤子函數)之上的,在原生事件和setTimeout 中不會批量更新,在「異步」中若是對同一個值進行屢次 setState , setState 的批量更新策略會對其進行覆蓋,取最後一次的執行,若是是同時 setState 多個不一樣的值,在更新時會對其進行合併批量更新。
合成事件,react爲了解決跨平臺,兼容性問題,本身封裝了一套事件機制,代理了原生的事件,像在jsx中常見的onClick、onChange這些都是合成事件 合成事件中也有batchedUpdates方法,是經過一樣的事務完成的
class App extends Component { state = { val: 0 } increment = () => { this.setState({ val: this.state.val + 1 }) console.log(this.state.val) // 輸出的是更新前的val --> 0 } render() { return ( <div onClick={this.increment}> {`Counter is: ${this.state.val}`} </div> ) } }
整個生命週期中就是一個事物操做,因此標識位isBatchingUpdates = true,因此流程到了enqueueUpdate()時,實例對象都會加入到dirtyComponents 數組中
class App extends Component { state = { val: 0 } componentDidMount() { this.setState({ val: this.state.val + 1 }) console.log(this.state.val) // 輸出的仍是更新前的值 --> 0 } render() { return ( <div> {`Counter is: ${this.state.val}`} </div> ) } }
原生事件是指非react合成事件,原生自帶的事件監聽 addEventListener ,或者也能夠用原生js、jq直接 document.querySelector().onclick 這種綁定事件的形式都屬於原生事件 原生事件綁定不會經過合成事件的方式處理,天然也不會進入更新事務的處理流程。setTimeout也同樣,在setTimeout回調執行時已經完成了原更新組件流程,不會放入dirtyComponent進行異步更新,其結果天然是同步的。
class App extends Component { state = { val: 0 } changeValue = () => { this.setState({ val: this.state.val + 1 }) console.log(this.state.val) // 輸出的是更新後的值 --> 1 } componentDidMount() { document.body.addEventListener('click', this.changeValue, false) } render() { return ( <div> {`Counter is: ${this.state.val}`} </div> ) } }
基於event loop的模型下, setTimeout 中裏去 setState 總能拿到最新的state值。api
class App extends Component { state = { val: 0 } componentDidMount() { setTimeout(_ => { this.setState({ val: this.state.val + 1 }) console.log(this.state.val) // 輸出更新後的值 --> 1 }, 0) } render() { return ( <div> {`Counter is: ${this.state.val}`} </div> ) } }
在 setState 的時候react內部會建立一個 updateQueue ,經過 firstUpdate 、 lastUpdate 、 lastUpdate.next 去維護一個更新的隊列,在最終的 performWork 中,相同的key會被覆蓋,只會對最後一次的 setState 進行更新數組
class App extends Component { state = { val: 0 } batchUpdates = () => { this.setState({ val: this.state.val + 1 }) this.setState({ val: this.state.val + 1 }) this.setState({ val: this.state.val + 1 }) } render() { return ( <div onClick={this.batchUpdates}> {`Counter is ${this.state.val}`} // 1 </div> ) } }
引起感想來源:dom