React中useState值為對象時改變值不渲染問題
useState值為對象時改變值不渲染
問題
修改State并重新setState(arr)后,值改變,但并未重新渲染
const [arr, setArr] = useState([]) arr.push(1) setArr(arr)
原因
React中默認淺監(jiān)聽,當(dāng)State值為對象時,棧中存的是對象的引用(地址),setState改變的是堆中的數(shù)據(jù)
所以此時 setArr(arr) 后,棧中的地址還是原地址,React淺監(jiān)聽到地址沒變,故會認為State并未改變,故沒有重渲染頁面
解決
思路:將棧中原arr所指向的地址改變即可
1)直接setState(要修改的值)
const [arr, setArr] = useState([]) setArr(1)
2)新創(chuàng)建一個數(shù)組newArr,將原數(shù)組的值賦值給新數(shù)組,并setState(newArr)
const [arr, setArr] = useState([]) const newArr = arr.slice(1) setArr(newArr)
3)利用ES6的拓展符…
const [arr, setArr] = useState([]) setArr([...arr])
useState用法指南
useState
const [state, setState] = useState(initialState);
返回一個 state,以及更新 state 的函數(shù)。
在初始渲染期間,返回的狀態(tài) (state) 與傳入的第一個參數(shù) (initialState) 值相同。
setState 函數(shù)用于更新 state。它接收一個新的 state 值并將組件的一次重新渲染加入隊列。
setState(newState);
在后續(xù)的重新渲染中,useState 返回的第一個值將始終是更新后最新的 state。
函數(shù)式更新
如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數(shù)傳遞給 setState。該函數(shù)將接收先前的 state,并返回一個更新后的值。下面的計數(shù)器組件示例展示了 setState 的兩種用法:
function Counter() { ? const [count, setCount] = useState(0); ? function handleClick() { ? ? setCount(count + 1) ? } ? function handleClickFn() { ? ? setCount((prevCount) => { ? ? ? return prevCount + 1 ? ? }) ? } ? return ( ? ? <> ? ? ? Count: {count} ? ? ? <button onClick={handleClick}>+</button> ? ? ? <button onClick={handleClickFn}>+</button> ? ? </> ? ); }
兩種方式的區(qū)別
注意上面的代碼,handleClick和handleClickFn一個是通過一個新的 state 值更新,一個是通過函數(shù)式更新返回新的 state。現(xiàn)在這兩種寫法沒有任何區(qū)別,但是如果是異步更新的話,那你就要注意了,他們是有區(qū)別的,來看下面例子:
function Counter() { ? const [count, setCount] = useState(0); ? function handleClick() { ? ? setTimeout(() => { ? ? ? setCount(count + 1) ? ? }, 3000); ? } ? function handleClickFn() { ? ? setTimeout(() => { ? ? ? setCount((prevCount) => { ? ? ? ? return prevCount + 1 ? ? ? }) ? ? }, 3000); ? } ? return ( ? ? <> ? ? ? Count: {count} ? ? ? <button onClick={handleClick}>+</button> ? ? ? <button onClick={handleClickFn}>+</button> ? ? </> ? ); }
當(dāng)我設(shè)置為異步更新,點擊按鈕延遲到3s之后去調(diào)用setCount函數(shù),當(dāng)我快速點擊按鈕時,也就是說在3s多次去觸發(fā)更新,但是只有一次生效,因為 count 的值是沒有變化的。
當(dāng)使用函數(shù)式更新 state 的時候,這種問題就沒有了,因為它可以獲取之前的 state 值,也就是代碼中的 prevCount 每次都是最新的值。
其實這個特點和類組件中 setState 類似,可以接收一個新的 state 值更新,也可以函數(shù)式更新。如果新的 state 需要通過使用先前的 state 計算得出,那么就要使用函數(shù)式更新。
因為setState更新可能是異步,當(dāng)你在事件綁定中操作 state 的時候,setState更新就是異步的。
class Counter extends React.Component { ? constructor(props) { ? ? super(props) ? ? this.state = { count: 0 } ? } ? handleClick = () => { ? ? this.setState({ count: this.state.count + 1 }) ? ? this.setState({ count: this.state.count + 1 }) ? ? // 這樣寫只會加1 ? } ? handleClickFn = () => { ? ? this.setState((prevState) => { ? ? ? return { count: prevState.count + 1 } ? ? }) ? ? this.setState((prevState) => { ? ? ? return { count: prevState.count + 1 } ? ? }) ? } ? render() { ? ? return ( ? ? ? <> ? ? ? ? Count: {this.state.count} ? ? ? ? <button onClick={this.handleClick}>+</button> ? ? ? ? <button onClick={this.handleClickFn}>+</button> ? ? ? </> ? ? ); ? } }
當(dāng)你在定時器中操作 state 的時候,而 setState 更新就是同步的。
class Counter extends React.Component { ? constructor(props) { ? ? super(props) ? ? this.state = { count: 0 } ? } ? handleClick = () => { ? ? setTimeout(() => { ? ? ? this.setState({ count: this.state.count + 1 }) ? ? ? this.setState({ count: this.state.count + 1 }) ? ? ? // 這樣寫是正常的,兩次setState最后是加2 ? ? }, 3000); ? } ? handleClickFn = () => { ? ? this.setState((prevState) => { ? ? ? return { count: prevState.count + 1 } ? ? }) ? ? this.setState((prevState) => { ? ? ? return { count: prevState.count + 1 } ? ? }) ? } ? render() { ? ? return ( ? ? ? <> ? ? ? ? Count: {this.state.count} ? ? ? ? <button onClick={this.handleClick}>+</button> ? ? ? ? <button onClick={this.handleClickFn}>+</button> ? ? ? </> ? ? ); ? } }
注意這里的同步和異步指的是 setState 函數(shù)。因為涉及到 state 的狀態(tài)合并,react 認為當(dāng)你在事件綁定中操作 state 是非常頻繁的,所以為了節(jié)約性能 react 會把多次 setState 進行合并為一次,最后在一次性的更新 state,而定時器里面操作 state 是不會把多次合并為一次更新的。
注意:與 class 組件中的 setState 方法不同,useState 不會自動合并更新對象。
性能優(yōu)化
React 使用 Object.is 比較算法來比較 state。
在 React 應(yīng)用中,當(dāng)某個組件的狀態(tài)發(fā)生變化時,它會以該組件為根,重新渲染整個組件子樹。
function Child({ onButtonClick, data }) { ? console.log('Child Render') ? return ( ? ? <button onClick={onButtonClick}>{data.number}</button> ? ) } function App() { ? const [number, setNumber] = useState(0) ? const [name, setName] = useState('hello') // 表單的值 ? const addClick = () => setNumber(number + 1) ? const data = { number } ? return ( ? ? <div> ? ? ? <input type="text" value={name} onChange={e => setName(e.target.value)} /> ? ? ? <Child onButtonClick={addClick} data={data} /> ? ? </div> ? ) }
如要避免不必要的子組件的重渲染,使用 React.memo 僅檢查 props 變更。 默認情況下其只會對復(fù)雜對象做淺層對比。所有使用 memo 優(yōu)化后的代碼如下:
function Child({ onButtonClick, data }) { ? console.log('Child Render') ? return ( ? ? <button onClick={onButtonClick}>{data.number}</button> ? ) } Child = memo(Child); // 在這里優(yōu)化了 function App() { ? const [number, setNumber] = useState(0) ? const [name, setName] = useState('hello') // 表單的值 ? const addClick = () => setNumber(number + 1) ? const data = { number } ? return ( ? ? <div> ? ? ? <input type="text" value={name} onChange={e => setName(e.target.value)} /> ? ? ? <Child onButtonClick={addClick} data={data} /> ? ? </div> ? ) }
你以為代碼中的Child = memo(Child);已經(jīng)優(yōu)化了嗎,然而并沒有,當(dāng)你在更改了父組件的狀態(tài),子組件依然會重新渲染,因為這關(guān)系到了React是如何淺層比較的,在子組件中onButtonClick 和 data 都是引用類型,所以他們是始終都不相等的,也就是[]===[]這樣比較時始終返回false,在基本數(shù)據(jù)類型比較時memo才會起作用。
關(guān)于如何解決這個問題,我們就要使用兩個新的API,useMemo和useCallback的Hook。下面是經(jīng)過優(yōu)化之后的代碼。
function Child({ onButtonClick, data }) { ? console.log('Child Render') ? return ( ? ? <button onClick={onButtonClick}>{data.number}</button> ? ) } Child = memo(Child) function App() { ? const [number, setNumber] = useState(0) ? const [name, setName] = useState('hello') // 表單的值 ? const addClick = useCallback(() => setNumber(number + 1), [number]) ? const data = useMemo(() => ({ number }), [number]) ? return ( ? ? <div> ? ? ? <input type="text" value={name} onChange={e => setName(e.target.value)} /> ? ? ? <Child onButtonClick={addClick} data={data} /> ? ? </div> ? ) } export default App;
把“創(chuàng)建”函數(shù)和依賴項數(shù)組作為參數(shù)傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優(yōu)化有助于避免在每次渲染時都進行高開銷的計算。如果沒有提供依賴項數(shù)組,useMemo 在每次渲染時都會計算新的值。
useCallback返回一個 memoized 回調(diào)函數(shù)。useCallback(fn, deps) 相當(dāng)于 useMemo(() => fn, deps)。
useCallback 和 useMemo 參數(shù)相同,第一個參數(shù)是函數(shù),第二個參數(shù)是依賴項的數(shù)組。主要區(qū)別是 React.useMemo 將調(diào)用 fn 函數(shù)并返回其結(jié)果,而 React.useCallback 將返回 fn 函數(shù)而不調(diào)用它。
總結(jié)
以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關(guān)文章
react源碼層分析協(xié)調(diào)與調(diào)度
本文主要介紹了深入理解React協(xié)調(diào)與調(diào)度(Scheduler)原理,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧2022-10-10react清空ant.design中表單內(nèi)容的方法實現(xiàn)
本文主要介紹了react清空ant.design中表單內(nèi)容的方法實現(xiàn),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-12-12reactjs學(xué)習(xí)解決unknown at rule @tailwind css
這篇文章主要介紹了reactjs學(xué)習(xí)解決unknown at rule @tailwind css問題,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2023-02-02詳解使用create-react-app快速構(gòu)建React開發(fā)環(huán)境
這篇文章主要介紹了詳解使用create-react-app快速構(gòu)建React開發(fā)環(huán)境,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-05-05React中完整實例講解Recoil狀態(tài)管理庫的使用
這篇文章主要介紹了React中Recoil狀態(tài)管理庫的使用,Recoil的產(chǎn)生源于Facebook內(nèi)部一個可視化數(shù)據(jù)分析相關(guān)的應(yīng)用,在使用React的實現(xiàn)的過程中,因為現(xiàn)有狀態(tài)管理工具不能很好的滿足應(yīng)用的需求,因此催生出了Recoil,對Recoil感興趣可以參考下文2023-05-05react Input組件Compositionstart和Compositionend事件
這篇文章主要為大家介紹了Compositionstart和Compositionend事件之于react組件庫Input組件的坑解決,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-11-11React?中使用?RxJS?優(yōu)化數(shù)據(jù)流的處理方案
這篇文章主要為大家介紹了React?中使用?RxJS?優(yōu)化數(shù)據(jù)流的處理方案示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-02-02