欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

React中setState同步異步場景的使用

 更新時間:2022年03月21日 11:17:15   作者:WindrunnerMax  
本文主要介紹了React中setState同步異步場景的使用,文中根據(jù)實例編碼詳細介紹的十分詳盡,具有一定的參考價值,感興趣的小伙伴們可以參考一下

setState同步異步場景

React通過this.state來訪問state,通過this.setState()方法來更新state,當this.setState()方法被調(diào)用的時候,React會重新調(diào)用render方法來重新渲染UI。相比較于在使用Hooks完成組件下所需要的心智負擔,setState就是在使用class完成組件下所需要的心智負擔,當然所謂的心智負擔也許叫做所必須的基礎知識更加合適一些。

描述

setState只在合成事件和生命周期鉤子函數(shù)中是異步的,而在原生事件中都是同步的,簡單實現(xiàn)一個React Class TS例子。

import React from "react";
import "./styles.css";

interface Props{}
interface State{
  count1: number;
  count2: number;
  count3: number;
}
export default class App extends React.Component<Props, State>{
  constructor(props: Props){
    super(props);
    this.state = {
      count1: 0,
      count2: 0,
      count3: 0
    }
  }
  
  incrementAsync = () => {
    console.log("incrementAsync before setState", this.state.count1);
    this.setState({
      count1: this.state.count1 + 1
    });
    console.log("incrementAsync after setState", this.state.count1);
  }
  incrementSync = () => {
    setTimeout(() => {
      console.log("incrementSync before setState", this.state.count2);
      this.setState({
        count2: this.state.count2 + 1
      });
      console.log("incrementSync after setState", this.state.count2);
    },0);
  }
  incrementAsyncFn = () => {
    console.log("incrementAsyncFn before setState", this.state.count3);
    this.setState({
        count3: this.state.count3 + 1
        },
        () => {
            console.log("incrementAsyncFn after.1 setState", this.state.count3);
        }
    );
    this.setState(state => {
      console.log("incrementAsyncFn after.2 setState", state.count3);
      return {count3: state.count3}
    });
  }
  render(){
    return <div>
      <button onClick={this.incrementAsync}>異步</button>
      <button onClick={this.incrementSync}>同步</button>
      <button onClick={this.incrementAsyncFn}>異步函數(shù)參數(shù)</button>
    </div>
  }
}

以此點擊三個按鈕的話,可以得到以下輸出。

incrementAsync before setState 0
incrementAsync after setState 0
incrementSync before setState 0
incrementSync after setState 1
incrementAsyncFn before setState 0
incrementAsyncFn after.2 setState 1
incrementAsyncFn after.1 setState 1

首先看incrementAsync的結(jié)果,在這里我們可以看出,在合成事件調(diào)用setState之后,this.state是無法立即得到最新的值。
對于incrementSync的結(jié)果,在非合成事件的調(diào)用時,this.state是可以立即得到最新的值的,例如使用addEventListener、setTimeoutsetInterval等。
對于incrementAsyncFn的兩個結(jié)果,首先來說after.2的結(jié)果,對于this.state也是可以得到最新的值的,如果你需要基于當前的state來計算出新的值,那么就可以通過傳遞一個函數(shù),而不是一個對象的方式來實現(xiàn),因為setState的調(diào)用是分批的,所以通過傳遞函數(shù)可以鏈式地進行更新,當然前提是需要確保它們是一個建立在另一個之上的,也就是說傳遞函數(shù)的setState的值是依賴于上次一的SetState的,對于after.1的結(jié)果,setState函數(shù)的第二個參數(shù)是一個回調(diào)函數(shù),在setState批量更新完成后可以拿到最新的值,而after.2也是屬于批量更新需要調(diào)用的函數(shù),所以after.1會在after.2后執(zhí)行。

原理

React將其實現(xiàn)為異步的動機主要是性能的考量,setState的異步并不是說內(nèi)部由異步代碼實現(xiàn),其實本身執(zhí)行的過程和代碼都是同步的,只是合成事件和生命周期鉤子函數(shù)的調(diào)用順序在批處理更新之前,導致在合成事件和生命周期鉤子函數(shù)中沒法立馬拿到更新后的值,形式了所謂的異步,實際上是否進行批處理是由其內(nèi)部的isBatchingUpdates的值來決定的。
setState依賴于合成事件,合成事件指的是React并不是將click等事件直接綁定在DOM上面,而是采用事件冒泡的形式冒泡到頂層DOM上,類似于事件委托,然后React將事件封裝給正式的函數(shù)處理運行和處理。說完了合成事件再回到setState,setState的批量更新優(yōu)化也是建立在合成事件上的,其會將所有的setState進行批處理,如果對同一個值進行多次 setState,setState的批量更新策略會對其進行覆蓋,取最后一次的執(zhí)行,如果是同時setState多個不同的值,在更新時也會對其進行合并批量更新,而在原生事件中,值會立即進行更新。
采用批量更新,簡單來說就是為了提升性能,因為不采用批量更新,在每次更新數(shù)據(jù)都會對組件進行重新渲染,舉個例子,讓我們在一個方法內(nèi)重復更新一個值。

this.setState({ msg: 1 });
this.setState({ msg: 2 });
this.setState({ msg: 3 });

事實上,我們真正想要的其實只是最后一次更新而已,也就是說前三次更新都是可以省略的,我們只需要等所有狀態(tài)都修改好了之后再進行渲染就可以減少一些性能損耗。還有一個例子,如果更改了N個狀態(tài),其實只需要一次setState就可以將DOM更新到最新,如果我們更新多個值。

this.setState({ msg: 1 });
this.setState({ age: 2 });
this.setState({ name: 3 });

此處我們分三次修改了三種狀態(tài),但其實React只需要渲染一次,在setState批處理之后會將其合并,并進行一次re-render就可以將整個組件的DOM更新到最新,根本不需要關心這個setState到底是從哪個具體的狀態(tài)發(fā)出來的。

那么還有一個問題,首先我們可以認同進行批處理更新對我們的性能是有益的,例如ChildParent都調(diào)用setState,我們不需要重新渲染Child兩次。但是此時我們可能會想到一個問題,為什么不能如同Vue一樣,Vue是在值更新之后觸發(fā)setter然后進行更新,更新的過程同樣也是采用異步渲染,也會將所有觸發(fā)Watcherupdate進行去重合并再去更新視圖,也就是說Vue是立即修改了值,而后再去更新視圖的。也就是說,相比較于React,為什么不能在同樣做批處理的情況下,立即將setState更新寫入this.state而不等待協(xié)調(diào)結(jié)束。

任何一種解決方案都有權衡,對于Vue來說因為其是通過劫持了數(shù)據(jù)的setter過程,在使用的也是直接使用=直接賦值的,而在賦值之后進行視圖的更新也是一個自然的過程,如果類似于React一樣在=之后這個值依然沒有變化,在直覺上是非常不符合常理的,雖然Vue是通過劫持setter實現(xiàn)的視圖更新,但是做到如同React一樣并不是不可能的,這是Vue采用的解決方案上的權衡,當然這只是可能的一個理由,說是問題的權衡,實際上還是需要對比React來看,對于React中要處理的問題,Vue自然會有自己解決方案的權衡,歸根到底還是框架的設計哲學的問題。對于上面提出的在同樣做批處理的情況下,立即將setState更新寫入this.state而不等待協(xié)調(diào)結(jié)束的這個問題,dan給予了兩個理由,在此簡作總結(jié),完整的英文版本還請看參考中的github issue。

保證內(nèi)部數(shù)據(jù)統(tǒng)一

即使state是同步更新的,但props是不會的,在重新渲染父組件之前,無法知道props,如果同步執(zhí)行此操作,批處理就會消失?,F(xiàn)在React提供的對象state、propsrefs在內(nèi)部是一致的。這意味著如果只使用這些對象,則可以保證它們引用完全協(xié)調(diào)的樹,即使它是該樹的舊版本。當僅使用state時,同步刷新的模式將起作用。

console.log(this.state.value); // 0
this.setState({ value: this.state.value + 1 });
console.log(this.state.value); // 1
this.setState({ value: this.state.value + 1 });
console.log(this.state.value); // 2

但是,假設需要提升此狀態(tài)以在幾個組件之間共享,因此將其移動到父級,也就是說有props參與到了傳值,那么同步setState模式就會有問題,此時將state提升到了父組件,利用props將值傳導子組件。

console.log(this.props.value); // 0
this.props.onIncrement();
console.log(this.props.value); // 0
this.props.onIncrement();
console.log(this.props.value); // 0

這是因為在這個解決方案中,this.state會立即刷新,而this.props不會,而且我們不能在不重新渲染父對象的情況下立即刷新this.props,這意味著我們將不得不放棄批處理的策略。還有更微妙的情況說明這如何破壞一致性的,例如這種方案正在混合來自props尚未刷新和state建議立即刷新的數(shù)據(jù)以創(chuàng)建新狀態(tài)。在React中,this.statethis.props都只在協(xié)調(diào)和刷新之后更新,所以你會在refactoring之前和之后看到0被打印出來。這使得提升狀態(tài)安全。在某些情況下這可能會帶來不便,特別是對于來自更多OO背景的人來說,他們只想多次改變狀態(tài),而不是考慮如何在一個地方表示完整的狀態(tài)更新,我可以理解這一點,盡管我確實認為從調(diào)試的角度來看,保持狀態(tài)更新的集中更加清晰??偠灾?,React模型并不總是會產(chǎn)生最簡潔的代碼,但它在內(nèi)部是一致的,并確保提升狀態(tài)是安全的。

啟用并發(fā)更新

從概念上講React的行為就好像每個組件都有一個更新隊列,我們在這里討論是否同步刷新state有一個前提那就是我們默認更新節(jié)點是遵循特定的順序的,但是按默認順序更新組件在以后的react中可能就變了。對于現(xiàn)在我們一直在談論的異步渲染,我承認我們在傳達這意味著什么方面做得不是很好,但這就是研發(fā)的本質(zhì):你追求一個在概念上看起來很有前途的想法,但只有在花了足夠的時間之后才能真正理解它的含義。
對于這個理由,是React發(fā)展的一個方向。我們一直在解釋異步渲染的一種方式是React可以根據(jù)setState()調(diào)用的來源分配不同的優(yōu)先級:事件處理程序、網(wǎng)絡響應、動畫等。例如你現(xiàn)在正在打字,那么TextBox組件需要實時的刷新,但是當你在輸入的時候,來了一個信息,這個時候可能讓信息的渲染延遲到某個閾值,而不是因為阻塞線程而讓輸入卡頓。如果我們讓某些更新具有較低優(yōu)先級,我們可以將它們的渲染分成幾毫秒的小塊,這樣用戶就不會注意到它們。異步rendering不僅僅是性能上的優(yōu)化,我們認為這是React組件模型可以做什么的根本性轉(zhuǎn)變。例如,考慮從一個屏幕導航到另一個屏幕的情況,通常會在渲染新屏幕時顯示一個導航器,但是如果導航速度足夠快,閃爍并立即隱藏導航器會導致用戶體驗下降,更糟糕的是如果有多個級別的組件具有不同的異步依賴項例如數(shù)據(jù)、代碼、圖像等,您最終會得到一連串短暫閃爍的導航器。由于所有的DOM重排,這既在視覺上令人不快,又使您的應用程序在實踐中變慢。如果當您執(zhí)行一個簡單的setState()來呈現(xiàn)不同的視圖時,我們可以開始在后臺呈現(xiàn)更新后的視圖。如果您自己不編寫任何協(xié)調(diào)代碼,您可以選擇在更新時間超過某個閾值時顯示導航器,否則當整個新子樹的異步依賴項是時讓React執(zhí)行無縫轉(zhuǎn)換使?jié)M意。請注意,這只是可能的,因為this.state不會立即刷新,如果它被立即刷新,我們將無法開始在后臺渲染視圖的新版本,而舊版本仍然可見且可交互,他們獨立的狀態(tài)更新會發(fā)生沖突。

參考

https://www.jianshu.com/p/cc12e9a8052c
https://juejin.cn/post/6844903636749778958
https://zh-hans.reactjs.org/docs/faq-state.html
https://blog.csdn.net/zz_jesse/article/details/118282921
https://blog.csdn.net/weixin_44874595/article/details/104270057

到此這篇關于React中setState同步異步場景的使用的文章就介紹到這了,更多相關React setState同步異步內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

最新評論