網站首頁 編程語言 正文
useState值為對象時改變值不渲染
問題
修改State并重新setState(arr)后,值改變,但并未重新渲染
const [arr, setArr] = useState([])
arr.push(1)
setArr(arr)
原因
React中默認淺監聽,當State值為對象時,棧中存的是對象的引用(地址),setState改變的是堆中的數據
所以此時 setArr(arr) 后,棧中的地址還是原地址,React淺監聽到地址沒變,故會認為State并未改變,故沒有重渲染頁面
解決
思路:將棧中原arr所指向的地址改變即可
1)直接setState(要修改的值)
const [arr, setArr] = useState([])
setArr(1)
2)新創建一個數組newArr,將原數組的值賦值給新數組,并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 的函數。
在初始渲染期間,返回的狀態 (state) 與傳入的第一個參數 (initialState) 值相同。
setState 函數用于更新 state。它接收一個新的 state 值并將組件的一次重新渲染加入隊列。
setState(newState);
在后續的重新渲染中,useState 返回的第一個值將始終是更新后最新的 state。
函數式更新
如果新的 state 需要通過使用先前的 state 計算得出,那么可以將函數傳遞給 setState。該函數將接收先前的 state,并返回一個更新后的值。下面的計數器組件示例展示了 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>
? ? </>
? );
}
兩種方式的區別
注意上面的代碼,handleClick和handleClickFn一個是通過一個新的 state 值更新,一個是通過函數式更新返回新的 state?,F在這兩種寫法沒有任何區別,但是如果是異步更新的話,那你就要注意了,他們是有區別的,來看下面例子:
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>
? ? </>
? );
}
當我設置為異步更新,點擊按鈕延遲到3s之后去調用setCount函數,當我快速點擊按鈕時,也就是說在3s多次去觸發更新,但是只有一次生效,因為 count 的值是沒有變化的。
當使用函數式更新 state 的時候,這種問題就沒有了,因為它可以獲取之前的 state 值,也就是代碼中的 prevCount 每次都是最新的值。
其實這個特點和類組件中 setState 類似,可以接收一個新的 state 值更新,也可以函數式更新。如果新的 state 需要通過使用先前的 state 計算得出,那么就要使用函數式更新。
因為setState更新可能是異步,當你在事件綁定中操作 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>
? ? ? </>
? ? );
? }
}
當你在定時器中操作 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 函數。因為涉及到 state 的狀態合并,react 認為當你在事件綁定中操作 state 是非常頻繁的,所以為了節約性能 react 會把多次 setState 進行合并為一次,最后在一次性的更新 state,而定時器里面操作 state 是不會把多次合并為一次更新的。
注意:與 class 組件中的 setState 方法不同,useState 不會自動合并更新對象。
性能優化
React 使用 Object.is 比較算法來比較 state。
在 React 應用中,當某個組件的狀態發生變化時,它會以該組件為根,重新渲染整個組件子樹。
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 變更。 默認情況下其只會對復雜對象做淺層對比。所有使用 memo 優化后的代碼如下:
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 = () => 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);已經優化了嗎,然而并沒有,當你在更改了父組件的狀態,子組件依然會重新渲染,因為這關系到了React是如何淺層比較的,在子組件中onButtonClick 和 data 都是引用類型,所以他們是始終都不相等的,也就是[]===[]這樣比較時始終返回false,在基本數據類型比較時memo才會起作用。
關于如何解決這個問題,我們就要使用兩個新的API,useMemo和useCallback的Hook。下面是經過優化之后的代碼。
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;
把“創建”函數和依賴項數組作為參數傳入 useMemo,它僅會在某個依賴項改變時才重新計算 memoized 值。這種優化有助于避免在每次渲染時都進行高開銷的計算。如果沒有提供依賴項數組,useMemo 在每次渲染時都會計算新的值。
useCallback返回一個 memoized 回調函數。useCallback(fn, deps) 相當于 useMemo(() => fn, deps)。
useCallback 和 useMemo 參數相同,第一個參數是函數,第二個參數是依賴項的數組。主要區別是 React.useMemo 將調用 fn 函數并返回其結果,而 React.useCallback 將返回 fn 函數而不調用它。
總結
原文鏈接:https://blog.csdn.net/luotouzi/article/details/119651201
相關推薦
- 2022-03-17 docker-compose安裝yml文件配置方式_docker
- 2022-03-14 @ConfigurationProperties獲取參數值
- 2023-04-18 Python實現常見的4種坐標互相轉換_python
- 2023-04-24 NumPy矩陣乘法的實現_python
- 2022-08-29 .NET?Core讀取配置文件_實用技巧
- 2022-11-15 .NETCore基于RabbitMQ實現延時隊列的兩方法_實用技巧
- 2022-05-18 python?中賦值,深拷貝,淺拷貝的區別_python
- 2022-09-26 C++繼承關系下的構造與析構
- 最近更新
-
- 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同步修改后的遠程分支