React 中setState更新state什麼時候同步什麼時候異步?

先說結論

  • React控制的事件處理程序,以及生命週期內調用setState是異步更新state
  • React控制以外的事件中調用setState是同步更新state,好比原生js綁定事件、setTimeout/setInrerval等。

setState的「異步」並非說內部由異步代碼實現,自己的執行過程和代碼都是同步的。算法

之因此會有一種異步方法的表現形式,歸根結底仍是由於React框架自己的性能機制所致使的。由於每次調用setState都會觸發更新,異步操做是爲了提升性能,將多個狀態合併一塊兒更新,減小re-render調用。性能優化

咱們來看一些例子:框架

React封裝事件

state = {
  number: 1
}

componentDidMount() {
  this.setState({ number: 3 });
  console.log(this.state.number); // 1
}

因而可知該事件處理程序中的setState是異步更新state的。異步

setTimeout

state = {
  number: 1
}

componentDidMount() {
  setTimeout(() => {
    this.setState({ number: 3 });
  }, 0);
  console.log(this.state.number); // 3
}

上面咱們講到,setState自己並非一個異步方法,之因此會變現出一種異步的形式,是由於React框架自己的一種性能優化機制。那麼基於這一點,假如咱們能繞過React的機制,就能夠令setState以同步的形式體現。函數

原生事件

state = {
  number: 1
}

componentDidMount() {
  document.body.addEventListener('click', this.resetState, false);
}

resetState() {
  this.setState({ number: 3 });
  console.log(this.state.number); // 3
}

一樣的,原生事件也能夠繞過React的性能優化機制,達到同步更新的表現。性能

React是如何控制異步和同步的?

ReactsetState函數實現中,會根據一個變量isBatchingUpdates判斷是否直接更新this.state,仍是放入隊列中延時更新。優化

isBatchingUpdates默認是false,標識setState是同步更新this.state。可是有一個函數batchedUpdates會把isBatchingUpdates修改成true,而當React在調用事件處理函數以前就會先調用這個函數將isBatchingUpdates修改成true。這樣由React控制的事件處理過程setState就不會同步更新this.statethis

function enqueueUpdate(component){
  //injected注入的
  ensureInjected();
  //若是不處於批量更新模式
  if(!batchingStrategy.isBatchingUpdates){
    batchingStrategy.batchedUpdates(enqueueUpdate, component);
    return;
  }
  //若是處於批量更新模式
  dirtyComponents.push(component);
}

image

事實上setState內部的執行過程是很複雜的,大體過程包括更新state,建立新的VNode,再通過diff算法對比差別,決定渲染哪一部分以及怎麼渲染,最終造成最新的UI。這一過程包含組件的四個生命週期函數:spa

  • shouComponentUpdate
  • componentWillUpdate
  • render
  • componentDidUpdate

若是是子組件而且依賴父組件,還會執行一個鉤子函數componentWillReceivePropscode

假如setState是同步更新的,每次更新這個過程都要完整執行一次,無疑會形成性能問題。事實上這些生命週期爲純函數,對性能還好,可是diff比較、更新DOM總消耗時間和性能吧。

在「異步」中若是對同一個值進行屢次setStatesetState 的批量更新策略會對其進行覆蓋,取最後一次的執行。

state = {
  number: 1
}

handleClick() {
  const number = this.state.number;

  this.setState({ number: number + 1 });
  this.setState({ number: number + 1 });
  this.setState({ number: number + 1 });
}

最終的結果只加了1。

若是是同時 setState 多個不一樣的值,在更新時會對其進行合併批量更新。

hanldeClick() {
  this.setState({ name: 'Clearlove' });
  this.setState({ age: 18 });
}

hanldeClick處理程序中調用了兩次setState,可是render只執行了一次。由於React會將多個this.setState產生的修改放在一個隊列裏進行批延時處理。

如何獲取「異步」更新後的數據?

setState提供了一個回調函數供開發者使用,在回調函數中,咱們能夠實時的獲取到更新以後的數據。仍是以剛纔的例子作示範:

state = {
  number: 1
}

componentDidMount() {
  this.setState({ number: 3 }, () => {
    console.log(this.state.number); // 3
  });
}
相關文章
相關標籤/搜索