網站首頁 編程語言 正文
類組件中的state
setState的用法
React項目中UI改變來源于state的改變,類組件中setState是更新組件,渲染視圖的主要方式
基本用法
setState(obj,callback)
- 第一個參數:當obj是一個對象,即為即將合并的state;如果obj是一個函數,那么當組件的state和props將作為參數,返回值用于合并新的state
- 第二個參數callback:callback為一個函數,函數執行上下文中可以獲取當前setState更新后的最新的值,可以作為依賴state變化的副作用函數,可以用來做一些基本的DOM操作
/* 第一個參數為function類型 */
this.setState((state,props)=>{
return {number:}
})
/* 第一個參數為object類型 */
this.setState({ number:1 },()=>{
console.log(this.state.number) //獲取最新的number
})
加入一次事件中觸發一次如上的setState,在React底層主要做了哪些事呢?
- 首先,setState會產生當前更新的優先級------產生更新優先級
- 接下來React會從fiber Root根部fiber向下調和子節點,調和階段將對比發生更新的彼方,更新對比expirationTime,找到發生更新的組件,合并state,然后觸發render函數,得到最新的UI視圖,完成render階段--------對比
- 接下來到commit階段,commit階段,替換真實DOM,完成此次更新流程。--------替換真實DOM
- 此時仍然在commit階段,會執行setState中callback函數,如上的()=>{console.log(this.state.number)} ,到此為止就完成了一次setState的過程。
更新的流層圖如下:
要記住一個主要任務的先后順序,這對于弄清渲染過程會有幫助:
render階段render函數執行--->commit階段真實DOM替換--->setState回調函數執行callback
類組件如何限制state更新視圖
對于類組件如何顯示state帶來的更新作用呢?
- pureComponet可以對state和props進行淺比較,如果沒有發生變化,那么組件就不會更新
- shouldComponentUpdate生命周期可以通過判斷前后state變化來決定組件需不需要更新,需要更新返回true,否則返回false
setState原理揭秘
知其然,知其所以然,下面將介紹setState的底層邏輯,要弄清楚state的更新機制,所以接下來要從兩個方向分析
- 一是揭秘enqueueSetState到底做了什么?
- 二是React底層是如何進行批量更新的?
首先,這里極簡了一下enqueueSetState的代碼,如下:
enqueueSetState(){
//每次調用setState,react都會創建一個update里面保存了如下
const update= createUpdate(expirationTime,suspenseConfig)
//callback 可以理解為setState回調函數,第二個參數
callback && (update.callback=callback)
//enqueuUpdate 把當前的update 傳入當前fier ,待更新隊列中
enqueuUpdate(fiber,update)
//開始調度更新
scheduleUpdateOnFiber(fiber,expirationTime)
}
enqueueSetState作用實際很簡單,就是創建一個update,然后放入當前的fiber對象的待更新隊列中,最后開啟調度更新,進入上述講到的更新流程。
那么問題來了,React的batchUpdate批量更新是什么時候加上去的呢?
這就要提前聊到事件系統了,正常的state更新,UI交互,都離不開用戶的事件,比如點擊事件,表單輸入等,React是采用事件合成的形式,每一個事件都是由React事件系統統一調度的,那么State批量更新正是和事件系統息息相關的。
//在legcy模式下,所有的事件都將經過此函數統一處理
function dispatchEventFoLegacyPluginEventSystem(){
//handleTopLevel 事件處理函數
batchEventUpdates(handleTopLvele,bookKeeping)
}
batchEventUpdates方法具體如下:
batchEventUpdate(fn,a){
//開啟批量更新
isBatchingEventUpdates=true;
try{
//這里執行了事處理函數,比如在一次點擊事件中觸發setState,那么它將在這個函數執行
return batchEventUpdateImpl(fn,a,b);
}finally{
//try里面的return 不會影響finally執行
//完成一次事件,批量更新
isBatchingEventUpdates=false
}
}
如上分析出流程圖,在React事件執行之前通過isBatchEventUpdates=true打開開關開啟事件批量更新,當該事件結束,再通過isBactchEventUpdates=false;關閉開關,然后在scheduleUpdateOnFiber中根據開關來確定是否進行批量更新
舉個例子,如下組件中這么寫:
import React, { Component } from 'react';
export default class Test extends Component {
state={number:0}
handleClick=()=>{
this.setState({number:this.state.number+1},()=>{
console.log('callback1',this.state.number)
})
console.log(this.state.number)
this.setState({number:this.state.number+1},()=>{
console.log('callback2',this.state.number)
})
console.log(this.state.number)
this.setState({number:this.state.number+1},()=>{
console.log('callback3',this.state.number)
})
console.log(this.state.number)
}
render() {
return (
<div>
{this.state.number}
<button onClick={this.handleClick}>number++</button>
</div>
);
}
}
點擊打印結果:0,0,0 callback1 1 ,callback2 1,callback3 1
如上代碼,在整個React上下文執行棧中會變成這樣:
那么,為什么異步操作里面的批量更新規則會被打破呢?比如用promise或者setTime在handleClick中這么寫:
handleClick=()=>{
setTimeout(()=>{
this.setState({number:this.state.number+1},()=>{
console.log('callback1',this.state.number)
})
console.log(this.state.number)
this.setState({number:this.state.number+1},()=>{
console.log('callback2',this.state.number)
})
console.log(this.state.number)
this.setState({number:this.state.number+1},()=>{
console.log('callback3',this.state.number)
})
console.log(this.state.number)
})
}
打印:callback1 1,1,callback2 2,2,callback3 3 ,3
那么整個React上下文執行棧就會變成如圖這樣
所以批量更新規則被打破。
那么,如果在如上異步環境下,繼續開啟批量更新模式呢?
React-Dom提供了批量更新方法unstable_batchChedUpdates,可以去手動批量更新,可以將上述setTimeout里面的內容作如下修改:
import ReactDom from 'react-dom';
const {unstable_batchedUpdates}=ReactDom
setTimeout(()=>{
unstable_batchedUpdates(()=>{
this.setState({number:this.state.number+1},()=>{
console.log('callback1',this.state.number)
})
console.log(this.state.number)
this.setState({number:this.state.number+1},()=>{
console.log('callback2',this.state.number)
})
console.log(this.state.number)
this.setState({number:this.state.number+1},()=>{
console.log('callback3',this.state.number)
})
console.log(this.state.number)
})
})
點擊打印結果:0,0,0 callback1 1 ,callback2 1,callback3 1
在實際工作中,unstable_batchChedUpdates可以用于Ajax數據交互之后,合并多次setState,或者是多次useState。原因很簡單,所有的數據交互都是在異步環境下,如果沒有批量更新處理,一次數據交互多次改變state會促使視圖多次渲染。
那么如何提升更新優先級呢?
React-dom提供了flushSync,flushSync可以將回調函數中的更新任務,放在一個比較高的優先級中。React設定了很多不同優先級的更新任務,如果一次更新任務在flushSync回調內部,那么將獲得一個比較高優先級的更新。
函數組件中的state
React-hooks發布之后,useState可以使函數組件像類組件一樣擁有state,也就是說名函數組件可以通過useState來改變UI視圖。那么useState到底應該如何使用,底層優勢怎么運行的呢?
useState的用法
const [state,setState] = useState(initData)
- state,目的是提供給UI,作為渲染視圖的數據源
- setState改變state的函數,可以理解為推動函數組件渲染的渲染函數
- initData有兩種情況,第一種情況是非函數,將作為state的初始化的值,第二種情況是函數,函數返回值作為作為useState初始化的值
initData為函數的情況:
const [number,setNumber]= React.useState(0)
initData為函數的情況:
const [number,setNumber]=React.useState(()=>{
//props中的a=1 state為0-1隨機數
//props中a=2 state為1-10的隨機數
//否則 state為1-100的隨機數
if(props.a===1) return Math.random()
if(props.a===2) return Math.ceil(Math.random()*10)
return Math.ceil(Math.random()*100)
})
對于setState參數,也有兩種情況:
- 第一種是非函數情況,此時將作為新的值,賦予state,作為下一次渲染使用;
- 第二種是函數的情況,如果setState的參數是一個函數,這里可以稱它為reducer,reducer參數,是上一次返回最新的state,返回值作為新的state
setState參數是一個非函數的情況:
const [number,setNumber]= React.useState(0)
const handleClick=()=>{
setNumber(1)
setNumber(2)
setNumber(3)
}
setState參數是一個函數的情況:
const [number,setNumber]= React.useState(0)
const handleClick=()=>{
setNumber((state)=>{
return state+1//0+1=1
})
setNumber(8)//8
setNumber((state)=>{
return state+1//8+1=9
})
}
如何監聽state的變化
類組件中的setState中,有第二個參數callback或是生命周期函數componentDidUpdate可以檢測監聽到state改變或是組件更新。
那么在函數組件中,如何監聽state變化呢?這個時候就需要useEffect出場了,通常可以把state作為依賴項傳入useEffect第二個參數deps,但是注意useEffect初始化是會默認執行一遍。
import React,{useEffect, useState} from 'react'
import ReactDom from 'react-dom'
export default function Test() {
const [number,setNumber]= React.useState(0)
const handleClick=()=>{
ReactDom.flushSync(()=>{
setNumber(2)
})
setNumber(1)
setTimeout(()=>{
setNumber(3)
})
}
useEffect(()=>{
console.log('變化',number)
},[number])
console.log(number)
return (
<button onClick={handleClick}>text1</button>
)
}
執行結果:
setState(dispatch)更新特點
上述講到的批量更新和flushSync,在函數組件中,dispatch更新效果和類組件是一樣的,但是useState有一點值得注意,就是當帝愛用改變state的函數dispatch,在本次函數執行上下文中,是獲取不到state的值的,舉例如下:
const [number,setNumber]= React.useState(0)
const handleClick=()=>{
ReactDom.flushSync(()=>{
setNumber(2)
console.log(number)
})
setNumber(1)
console.log(number)
setNumber(()=>{
setNumber(3)
console.log(number)
})
}
結果:0 0 0
原因很簡單,函數組件更新就是函數的執行,在函數一次執行過程中,函數內部所有變量重新生命,所以改變的state,只有在下一次函數組件執行時才會更新,所以在如同上一個函數執行上下文中,number一直為0,無論怎么打印,都拿不到最新的state。
useState的注意事項
在使用useState的dispatchAction更新state的時候,記得不要傳入相同的state,這樣會使視圖不更新,比如下面:
const [state,dispatchState]= React.useState({
name:'aline'
})
const handleClick=()=>{
state.name='aline'
dispatchState(state)//直接改變state,在內存中執行的地址沒有變
}
上述例子為什么沒有更新呢?是因為在useState的dispatchAction處理邏輯中,會淺比較state兩次,發現state相同,不會開啟更新調度任務。其中demo中兩次state指向了相同的內存空間,所以默認為state相等,就不會發生視圖更新了
解決問題:把上述的dispatchState改成dispatch({...state})根本解決了問題,淺拷貝了對象,重新開啟了內存空間。
總結
類組件中的setState和函數組件中的useState有什么異同?
相同點
首先從原理角度出發,setState和useState更新視圖,底層都調用了scheduleUpdateOnFiber方法,而且時間驅動情況下都有批量更新規則
不同點
- 再不是pureComponent組件模式下,setState不會淺比較兩次的state的值,只有調用setState,在沒有其他優化手段的前提下,會執行更新,但是useState中的dispatchAction會默認比較兩次state是否相同,然后決定是否更新組件
- setState有專門監聽state變化的回調函數callback,著這個回調函數中可以獲取到最新的值,而在函數組件中,只能通過useEffect來執行state變化引起副作用。
- setState在頂層處理state的邏輯主要是和舊state進行合并操作,而useState則是替換,及重新賦值
原文鏈接:https://blog.csdn.net/weixin_46872121/article/details/127826246
相關推薦
- 2023-01-11 解決?Redis?數據傾斜、熱點等問題_Redis
- 2022-06-12 Pandas對CSV文件讀寫操作詳解_python
- 2022-07-30 注冊中心eureka的介紹及源碼探索
- 2022-12-07 R語言隨機抽樣詳解_R語言
- 2022-06-09 Redis超詳細講解高可用主從復制基礎與哨兵模式方案_Redis
- 2024-04-05 @Version樂觀鎖配置mybatis-plus使用(version)
- 2022-12-03 詳解QML?調用?C++?中的內容_C 語言
- 2022-11-20 Python利用pangu模塊實現文本格式化小工具_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支