深入React技術棧之setState詳解

2020-12-20 IT小精英

拋出問題

class Example extends Component { contructor () { super() this.state = { value: 0, index: 0 } } componentDidMount () { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第一次輸出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第二次輸出 setTimeout(() => { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第三次輸出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第四次輸出 }, 0); this.refs.button.addEventListener('click', this.click) } click = () => { this.setState({value: this.state.index + 1}) this.setState({value: this.state.index + 1}) } render () { return ( <div><span>value: {this.state.value}index: {this.props.index}</span> <button ref="button" onClick={this.click}>點擊</button> </div> ) }}這四次輸出,按常理來說分別是: 1,2,3,4。但是,實際輸出為: 0, 0, 2, 3setState的注意點

setState不會立刻改變React組件中state的值(即setState是異步更新)setState通過一個隊列機制實現state更新;當執行setState時,會將需要更新的state合併後放入狀態隊列,而不會立即更新,隊列可以高效的批量更新state;通過this.state直接修改的值,state不會放入狀態隊列,當下次調用setState並對狀態隊列進行合併時,會忽略之前直接被修改的state.setState通過引發一次組件的更新過程來引發重新繪製此處重繪指的就是引起React的更新生命周期函數4個函數:shouldComponentUpdate(被調用時this.state沒有更新;如果返回了false,生命周期被中斷,雖然不調用之後的函數了,但是state仍然會被更新)componentWillUpdate(被調用時this.state沒有更新)render(被調用時this.state得到更新)componentDidUpdate多個相鄰的state的修改可能會合併到一起一次執行this.setState({name: 'Pororo'}) this.setState({age: 20})等同於this.setState({name: 'Pororo',age: 20})上面兩塊代碼的效果是一樣的。如果每次調用都引發一次生命周期更新,那性能就會消耗很大了。所以,React會將多個this.setState產生的修改放進一個隊列裡,等差不多的時候就會引發一次生命周期更新。問題分析

對於前兩次setState:this.setState({value: this.state.val + 1});console.log(this.state.value); // 第一次輸出this.setState({value: this.state.val + 1});console.log(this.state.value); // 第二次輸出由於setState不會立即改變React組件中state的值,所以兩次setState中this.state.value都是同一個值0,故而,這兩次輸出都是0。因而value只被加1。既然這樣,那麼是不是可以直接操作this.state呢?比如:this.state.value=this.state.value+1;這樣的確可以修改this.state.value的狀態但是卻不可以引發重複渲染。所以,就必須通過React設定的setState函數去改變this.state,從而引發重新渲染。setTimeout裡面的兩次setState:setTimeout(() => { this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第三次輸出 this.setState({value: this.state.value + 1}) console.log(this.state.value) // 第四次輸出}, 0);這兩次this.state的值同步更新了;同步更新:是由React引發的事件處理(比如:onClick引發的事件處理),調用setState會異步更新this.state;異步更新:除此之外的setState調用會同步執行this.setState。 「除此之外」指的是:繞過React通過addEventListener直接添加的事件處理函數和setTimeout/setInterval產生的異步調用。this.setState更新機製圖解:

每次setState產生新的state會依次被存入一個隊列,然後會根據isBathingUpdates變量判斷是直接更新this.state還是放進dirtyComponent裡回頭再說。isBatchingUpdates默認是false,也就表示setState會同步更新this.state。但是,當React在調用事件處理函數之前就會調用batchedUpdates,這個函數會把isBatchingUpdates修改為true,造成的後果就是由React控制的事件處理過程setState不會同步更新this.state。同步更新(函數式setState)

如果this.setState的參數不是一個對象而是一個函數時,這個函數會接收到兩個參數,第一個是當前的state值,第二個是當前的props,這個函數應該返回一個對象,這個對象代表想要對this.state的更改;換句話說,之前你想給this.setState傳遞什麼對象參數,在這種函數裡就返回什麼對象。不過,計算這個對象的方法有些改變,不再依賴於this.state,而是依賴於輸入參數state。function increment(state, props) { return {count: state.count + 1};}function incrementMultiple() { this.setState(increment); this.setState(increment); this.setState(increment);}假如當前this.state.count的值是0,第一次調用this.setState(increment),傳給increment的state參數是0,第二調用時,state參數是1,第三次調用是,參數是2,最終incrementMultiple讓this.state.count變成了3。對於多次調用函數式setState的情況,React會保證調用每次increment時,state都已經合併了之前的狀態修改結果。要注意的是,在increment函數被調用時,this.state並沒有被改變,依然,要等到render函數被重新執行時(或者shouldComponentUpdate函數返回false之後)才被改變。

同步異步setState的用法混合

function incrementMultiple() { this.setState(increment); this.setState(increment); this.setState({count: this.state.count + 1}); this.setState(increment);}在幾個函數式setState調用中插入一個傳統式setState調用,最後得到的結果是讓this.state.count增加了2,而不是增加4。這是因為React會依次合併所有setState產生的效果,雖然前兩個函數式setState調用產生的效果是count加2,但是中間出現一個傳統式setState調用,一下子強行把積攢的效果清空,用count加1取代。所以,傳統式setState與函數式setState一定不要混用。

相關焦點

  • 【React源碼筆記】setState原理解析
    為什麼多次更新state的值會被合併只會觸發一次render?為什麼直接修改this.state無效???帶著這麼多的疑問,因為剛來需求也不多,對setState這一塊比較好奇,那我就默默clone了react源碼。今天從這四個有趣的問題入手,用setState跟大家深入探討state的更新機制,一睹setState的芳容。
  • React系列八 - 深入理解setState
    () {    this.state.message = "你好啊,李銀河";  }我們必須通過setState來更新數據:疑惑:在組件中並沒有實現setState的方法,為什麼可以調用呢?:可見setState是異步的操作,我們並不能在執行完setState之後立馬拿到最新的state的結果changeText() {  this.setState({    message: "你好啊,李銀河"  })  console.log(this.state.message); // Hello World
  • react源碼分析之-setState是異步還是同步?
    pre state', this.state.count); this.setState({ count: this.state.count + 1 }); console.log('setTimeout next state', this.state.count); }, 0); }
  • 面試題:React中setState是異步還是同步?
    在學習react的過程中幾乎所有學習材料都會反覆強調一點setState是異步的,來看一下react官網對於setState的說明。
  • 深入介紹 React 中的 state 和 props 更新
    = {count: 0};        this.handleClick = this.handleClick.bind(this);    }    handleClick() {        this.setState((state) => {            return {count: state.count + 1};
  • JavaScript教程:React Hook之useState和useEffect
    useState計數器實例:import React, { useState } from 'react';function Example() {// Declare a new state variable, which we'll call "count"const [count, setCount] = useState(0);return (<div>
  • React 16.8 之 React Hook
    那麼我們再看一下使用hook的版本import React, { useState } from 'react';function Example() {  // 聲明一個叫 "count" 的 state 變量  const [count, setCount] = useState
  • React 狀態管理庫的battle (setState/useState vs Redux vs Mobx)
    原文連結:https://dev.to/mpodlasin/my-thoughts-on-endless-battle-of-react-state-management-libraries-setstate-usestate-vs-redux-vs-mobx
  • 深入 React Fiber 內部
    ,當 a 中的 b 被調用時,它為 b 創建一個函數執行上下文並且 push 到棧中。 = {count: 0};        this.handleClick = this.handleClick.bind(this);    }    handleClick() {        this.setState((state) => {            return {count: state.count + 1};
  • 手寫React-Router源碼,深入理解其原理
    本文會繼續深入React-Router講講他的源碼,套路還是一樣的,我們先用官方的API實現一個簡單的例子,然後自己手寫這些API來替換官方的並且保持功能不變。React-Router的項目結構 React-Router的結構是一個典型的monorepo,monorepo這兩年開始流行了,是一種比較新的多項目管理方式,與之相對的是傳統的multi-repo。比如React-Router的項目結構是這樣的:
  • 前端技術:React&Vue對比
    React和vue的業務邏輯是差不多,vue在react上封裝了更簡潔的方法,使用起來更加的便捷,如:提供了便捷的指令(v-for,v-if,v-model),還提供了更多的屬性(computed,watch),我還是比較喜歡用react的,更接近js原生,更容易於理解它。
  • 3-6-react-redux
    或者單獨使用了redux,接下來呢,讓我們來繼續重構之前的計數器的例子,不過這一回呢,我們要正式地協同使用react和redux了:const counter = (state = 0, action) => {  switch (action.type) {    case 'INCREMENT':      return state + 1;
  • React知識點總結
    list5Copy = this.state.list5.slice() list5Copy.splice(2, 0, 'a') // 中間插入/刪除 this.setState({ list1: this.state.list1.concat(100), // 追加 list2: [...this.state.list2, 100]
  • React Hook 入門教程
    Hook 提供了問題的解決方案,無需學習複雜的函數式或響應式編程技術。函數組件組件的最佳寫法應該是函數,而不是 class。import React, { useState } from 'react';function Example() {  // 聲明一個叫 "count" 的 state 變量  const [count, setCount] = useState(0);  return (
  • 手寫ReactHook核心原理,再也不怕面試官問我ReactHook原理
    調用useState會返回一個state變量,以及更新state變量的方法。useState的參數是state變量的初始值,初始值僅在初次渲染時有效。更新state變量的方法,並不會像this.setState一樣,合併state。
  • 掌握react,這一篇就夠了
    自定義元素react強大之處就在於可以組件的自定義,實現組件的復用。如果我們創建了一個組件。我們也可以通過jsx語法調用。React數據流statestate是組件的內部狀態,需要在視圖裡面用到的狀態,才需要放到state裡面去。如下,我們在類上創建一個state屬性,在視圖裡面通過使用this.state.name去引用。而這裡的state定義則代替的是getinitialstate方法。
  • 30 分鐘精通 React 新特性——React Hooks
    但假如你在大型的工作項目中用react,你會發現你的項目中實際上很多react組件冗長且難以復用。尤其是那些寫成class的組件,它們本身包含了狀態(state),所以復用這類組件就變得很麻煩。那之前,官方推薦怎麼解決這個問題呢?答案是:渲染屬性(Render Props)和高階組件(Higher-Order Components)。我們可以稍微跑下題簡單看一下這兩種模式。
  • react的核心api-前端進階
    創建項目: create-react-app react-study 3. this.setState({counter: this.state.counter + 1}); this.setState({counter: this.state.counter + 1}); this.setState({counter: this.state.counter + 1});
  • React.js 2016最佳實踐
    import { fromJS } from 'immutable'  const state = fromJS({ bar: 'biz' })  const newState = foo.set('bar', 'baz')  Immutable.js非常快,其背後的思想也非常美妙。
  • 我對 React V16.4 生命周期的理解
    它接收兩個參數:info —— 帶有 componentStack key 的對象,其中包含有關組件引發錯誤的棧信息。componentDidCatch() 會在「提交」階段被調用,因此允許執行副作用。