React setState() 的原理解析

setState 的原理機制解析


我們本章節主要是要來分析一下React中常見的setState方法,熟悉React的小夥伴應該都知道,該方法通常用於改變組件狀態並用新的state去更新組件。但是,這個方法在很多地方的表現總是與我們的預期不符,先來看幾個案例。

常見案例

class Root extends Component {
  constructor(props) {
    super(props);
    this.state = {
      count: 0
    };
  }
  componentDidMount() {
    let that = this;

    that.setState({ count: that.state.count + 1 });
    console.log(that.state.count);    // 打印出 0

    that.setState({ count: that.state.count + 1 });
    console.log(that.state.count);    // 打印出 0

    setTimeout(function(){
     that.setState({ count: that.state.count + 1 });
     console.log(that.state.count);   // 打印出 2
    }, 0);

    setTimeout(function(){
     that.setState({ count: that.state.count + 1 });
     console.log(that.state.count);   // 打印出 3
    }, 0);

  }
  render() {
    return (
      <h1>{this.state.count}</h1>
    )
  }
}

這個案例的結果確實讓人非常意外,打印出 0,0,2,3。爲什麼沒有按照我們預期的1,2,3,4往下進行這樣的打印方式呢?我們接着往下看:

在瞭解setState之前,我們先來簡單瞭解下 React 一個包裝結構: Transaction:

事務 (Transaction):

它呢,是 React 中的一個調用結構,用於包裝一個方法,結構爲: initialize - perform(thatthod) - close。通過事務,可以統一管理一個方法的開始與結束;處於事務流中,表示進程正在執行一些操作;

圖片佔位符

設置狀態值 setState:

ReactsetState用於修改狀態,更新視圖。它具有以下特點:

異步與同步: setState 並不是單純的異步或同步,這其實與調用時的環境相關:

  • 在 合成事件 和 生命週期鉤子(除 componentDidUpdate ) 中,setState是"異步"的;

原因:

因爲在setState的實現中,有一個判斷: 當更新策略正在事務流的執行中時,該組件更新會被推入dirtyComponents隊列中等待執行;否則,開始執行batchedUpdates隊列更新;

  • 在生命週期鉤子調用中,更新策略都處於更新之前,組件仍處於事務流中,而componentDidUpdate是在更新之後,此時組件已經不在事務流中了,因此則會同步執行;
  • 在合成事件中,React 是基於 事務流完成的事件委託機制 實現,也是處於事務流中;

在這裏插入圖片描述

  • 問題: 無法在setState後馬上從this.state上獲取更新後的值。
    • 解決: 如果需要馬上同步去獲取新值,setState其實是可以傳入第二個參數的。setState(updater, callback),在回調中即可獲取最新值;
	setState({
    	index: 1
	}}, function(){
    	console.log(this.state.index);
	})
  • 在鉤子函數中體現:
	componentDidUpdate(){
	    console.log(this.state.index);
	}
  • 在 原生事件 和 setTithatout 中,setState 是同步的,可以馬上獲取更新後的值;

    • 原因: 原生事件是瀏覽器本身的實現,與事務流無關,自然是同步;而setTithatout是放置於定時器線程中延後執行,此時事務流已結束,因此也是同步;
  • 批量更新: 在 合成事件生命週期鉤子 中,setState更新隊列時,存儲的是 合併狀態(Object.assign)。因此前面設置的 key 值會被後面所覆蓋,最終只會執行一次更新;如下圖所示:

在這裏插入圖片描述

  • 函數式: 由於 Fiber 及 合併 的問題,官方推薦可以傳入 函數 的形式。setState(fn),在fn中返回新的state對象即可,例如this.setState((state, props) => newState);使用函數式,可以用於避免setState的批量更新的邏輯,傳入的函數將會被 順序調用
class Com extends Component{
    constructor(props){
        super(props);
        this.state = { index: 0 };
        this.add = this.add.bind(this);
    }

    add(){
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
        this.setState(prevState => {
            return {index: prevState.index + 1};
        });
    }
}

注意事項:

  1. setState 合併,在 合成事件生命週期鉤子多次連續調用會被優化爲一次

  2. 當組件已被銷燬,如果再次調用setStateReact 會報錯警告,通常有兩種解決辦法:

    • 將數據掛載到外部,通過 props 傳入,如放到 Redux 或 父級中;
    • 在組件內部維護一個狀態量(isUnmounted)componentWillUnmount中標記爲 true,在setState前進行判斷;
發佈了243 篇原創文章 · 獲贊 96 · 訪問量 38萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章