React系列九:深刻理解setState

快來加入咱們吧!

"小和山的菜鳥們",爲前端開發者提供技術相關資訊以及系列基礎文章。爲更好的用戶體驗,請您移至咱們官網小和山的菜鳥們 ( xhs-rookies.com/ ) 進行學習,及時獲取最新文章。前端

"Code tailor" ,若是您對咱們文章感興趣、或是想提一些建議,微信關注 「小和山的菜鳥們」 公衆號,與咱們取的聯繫,您也能夠在微信上觀看咱們的文章。每個建議或是贊同都是對咱們極大的鼓勵!react

前言

這節咱們將介紹 ReactsetState ,但願能夠幫助你們真正理解 setStategit

本文會向你介紹如下內容:github

  • 如何使用 setState
  • 不能直接修改 State
  • setState()
  • setState 多是異步更新
  • setState 的合併

如何使用 setState

在介紹 setState 以前,咱們先來看一個 setState 的案例,瞭解一下是如何使用的。web

咱們來展現一個使用案例,當點擊一個 改變文本 的按鈕時,修改界面前顯示的內容:服務器

默認展現

案例代碼

import React, { Component } from 'react'

export default class App extends Component {
  constructor(props) {
    super(props)

    this.state = {
      message: 'Hello React',
    }
  }

  render() {
    return (
      <div> <h2>{this.state.message}</h2> <button onClick={(e) => this.changeMessage()}>ChangeMessage</button> </div>
    )
  }

  changeMessage() {
    this.setState({
      message: 'Hello xhs,your message is changed.',
    })
  }
}
複製代碼

state 的初始化是在類構造器中去設置的,而後若是想要更改 state ,那就是經過 setState 函數,該函數最主要的就是傳入一個對象做爲參數,而該對象就是你想要修改的值。上述代碼中,當你點擊 ChangeMessage 時,就會調用 setState 函數,而 setState 會調用 render 函數,頁面就會被從新渲染。微信

點擊按鈕以後,從新渲染的效果:markdown

從新渲染以後

不能直接修改 State

將上面的 changeMessage 方法,改爲下面的樣子。dom

changeMessage() {
  this.state.message = "Hello xhs,your message is changed.";
}
複製代碼

點擊 ChangeMessage 以後,頁面並無改變,可是打印 state 你會發現,state 中的 message 變了,可是頁面不會被從新渲染。異步

構造函數,是咱們惟一能夠給 this.state 賦值的地方,而爲了能夠從新渲染頁面,那就只能經過 setState 函數來修改。

因此,咱們經過調用 setState 來修改數據:

  • 當咱們調用 setState 時,會從新執行 render 函數,根據最新的 State 來建立 ReactElement 對象
  • 再根據最新的 ReactElement 對象,對 DOM 進行修改;
changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  })
}
複製代碼

setState()

setState(updater, [callback])
複製代碼

setState() 將對組件 state 的更改排入隊列,並通知 React 須要使用更新後的 state 從新渲染此組件及其子組件。這是用於更新用戶界面以響應事件處理器和處理服務器數據的主要方式

setState() 視爲 請求 而不是當即更新組件的命令。爲了更好的感知性能,React 會延遲調用它,而後經過一次傳遞更新多個組件。React 並不會保證 state 的變動會當即生效。

setState() 並不老是當即更新組件。它會批量推遲更新。這使得在調用 setState() 後當即讀取 this.state 成爲了隱患。爲了消除隱患,請使用 componentDidUpdate 或者 setState 的回調函數(setState(updater, callback)),這兩種方式均可以保證在應用更新後觸發。

參數一能夠有兩種形式

1. 接受對象類型

setState(stateChange[, callback])
複製代碼

stateChange 會將傳入的對象淺層合併到新的 state 中,例如 讓 count +1

this.setState({ count: this.state.count + 1 })
複製代碼

這種形式的 setState() 也是異步的,而且在同一週期內會對多個 setState 進行批處理。例如,若是在同一週期內屢次 count + 1,則至關於:

Object.assign(
  previousState, // 以前的狀態
  {count: state.count + 1},
  {count: state.count + 1},
  ...
)
複製代碼

後調用的 setState() 將覆蓋同一週期內先調用 setState 的值,所以商品數僅增長一次。若是後續狀態取決於當前狀態,咱們建議使用 updater 函數 的形式代替:

this.setState((state) => {
  return { count: state.count + 1 }
})
複製代碼

2. 接受函數參數

咱們在初始的 state 中並無一個 count 。可是如今咱們有一個需求:那就是添加一個 countstate 中,使用對象做爲第一參數,你就會發現這樣一個問題。

changeCount () {
	this.setState({
		count: 0
	}) // => this.state.count 是 undefined
	this.setState({
		count: this.state.count + 1
	}) // => undefined + 1 = NaN
	this.setState({
		count: this.state.count + 2
	}) // => NaN + 2 = NaN
}
複製代碼

上面的代碼的運行結果並不能達到咱們的預期,咱們但願 count 運行結果是 3 ,但是最後獲得的是 NaN。可是這種後續操做依賴前一個 setState 的結果的狀況並不罕見。

這裏就天然地引出了 setState 的第二種使用方式,能夠接受一個函數做爲參數。React.js 會把上一個 setState 的結果傳入這個函數,你就可使用該結果進行運算、操做,而後返回一個對象做爲更新 state 的對象:

changeCount () {
	this.setState((prevState) => {
		return { count: 0 }
	})
	this.setState((prevState) => {
		return { count: prevState.count + 1 }
    // 上一個 setState 的返回是 count 爲 0,這裏須要執行 +1 因此當前返回 1
  })
	this.setState((prevState) => {
		return { count: prevState.count + 2 }
    // 上一個 setState 的返回是 count 爲 1,這裏須要執行 +2 因此當前返回 3
	})
  // 最後的結果是 this.state.count 爲 3
}
複製代碼

這樣就能夠達到上述的利用上一次 setState 結果進行運算的效果。

參數二爲回調函數

setState() 的第二個參數爲可選的回掉函數,它將在 setState 完成合並並從新渲染組件後執行。一般,咱們建議使用 componentDidUpdate() 來代替此方式。

咱們來給案例中的 changeMessage 函數添加兩個打印方式。

changeMessage() {
  this.setState({
		message: "Hello xhs,your message is changed."
	},() => {
    console.log('callback result: ',this.state.message);
  })
  console.log('no callback result:',this.state.message);
}
複製代碼

咱們來看看結果

在這裏插入圖片描述

從圖片中咱們能夠看出

  • setState 外部的時候,並不能夠立馬拿到咱們想要的結果
  • callback 中則返回的是修改以後的結果。

正由於不是立馬拿到咱們想要的結果,因此這就是咱們接下來要講的 setState 多是異步更新

這樣咱們就能夠知道 setState 的第二參數的做用,就是能夠確保獲得 state 已經修改以後的結果。也就是從新渲染以後執行的內容。

setState 多是異步更新

咱們來看下面的代碼:

  • 最終打印結果是 Hello React
  • 可見 setState 是異步的操做,咱們並不能在執行完 setState 以後立馬拿到最新的 state 的結果
changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  })
  console.log(this.state.message); // Hello React
}
複製代碼

爲何 setState 設計爲異步呢?

  • setState 設計爲異步其實以前在 GitHub 上也有不少的討論;
  • React 核心成員(Redux 的做者)Dan Abramov 也有對應的回覆,能夠參考一下;

咱們來對他的回答作一個簡單的總結:

  • setState 設計爲異步,能夠顯著的提高性能;

  • 若是每次調用 setState 都進行一次更新,那麼意味着 render 函數會被頻繁調用,界面從新渲染,這樣效率是很低的;

注意: 最好的辦法應該是獲取到多個更新,以後進行批量更新;

  • 若是同步更新了 state ,可是尚未執行 render 函數,那麼 stateprops 不能保持同步;

注意:stateprops 不能保持一致性,會在開發中產生不少的問題;

獲取到更新後的值

  • setState 的第二參數,一個回調函數,這個回調函數會在更新後會執行;
changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  }, () => {
    console.log(this.state.message); // Hello xhs,your message is changed.
  });
}
複製代碼

固然,咱們也能夠在生命週期函數:

componentDidUpdate(prevProps, provState, snapshot) {
  console.log(this.state.message);
}
複製代碼

setState 必定是異步?

疑惑:setState必定是異步更新的嗎?

驗證一:在 setTimeout 中的更新:

changeText() {
  setTimeout(() => {
    this.setState({
      message: "你好啊,小和山"
    });
    console.log(this.state.message); // 你好啊,小和山
  }, 0);
}
複製代碼

驗證二:原生 DOM 事件:

componentDidMount() {
  const btnEl = document.getElementById("btn");
  btnEl.addEventListener('click', () => {
    this.setState({
      message: "你好啊,小和山"
    });
    console.log(this.state.message); // 你好啊,小和山
  })
}
複製代碼

其實分紅兩種狀況:

  • 在組件生命週期或 React 合成事件中,setState 是異步;
  • setTimeout 或者原生 dom 事件中,setState 是同步;

setState 的合併

數據的合併

當你調用 setState() 的時候,React 會把你提供的對象合併到當前的 state

state 定義一些數據

this.state = {
  name: 'xhs',
  message: 'Hello React',
  count: 0,
}
複製代碼

經過 setState 去修改 state 中的 message ,是不會對 name 產生影響的

changeMessage() {
  this.setState({
    message: "Hello xhs,your message is changed."
  });
}
複製代碼

多個 setState 會被合併

好比咱們仍是有一個 count 屬性,默認爲 0,記錄當前的數字:

  • 執行下面的操做以後,最後的答案是 1
  • 多個 state 進行了合併
increment() {
	this.setState({
		count: this.state.count + 1
	});

  this.setState({
    count: this.state.count + 1
  });

  this.setState({
    count: this.state.count + 1
  });
}
複製代碼

如何能夠作到,讓 count 最終變成 3 呢?

increment() {
  this.setState((state, props) => {
    return {
      count: state.count + 1
    }
  })

  this.setState((state, props) => {
    return {
      count: state.count + 1
    }
  })

  this.setState((state, props) => {
    return {
      count: state.count + 1
    }
  })
}
複製代碼

上面的代碼就是用到了,setState 接收一個函數做爲第一參數的狀況,來解決這個問題,就能夠很好的獲得咱們期待的結果。

下節預告

本節咱們學習了 ReactSetState 其中的奧祕,在下一個章節咱們將繼續學習 React 中受控組件和非受控組件的內容,敬請期待!

相關文章
相關標籤/搜索