網站首頁 編程語言 正文
使用useCallback優化代碼
useCallback是對傳過來的回調函數優化,返回的是一個函數;useMemo返回值可以是任何,函數,對象等都可以。
簡單來說就是返回一個函數,只有在依賴項發生變化的時候才會更新(返回一個新的函數)。
1.原理分析
useCallback是React Hooks中的一個函數,用于優化函數組件的性能。它的作用是返回一個memoized(記憶化的)函數,這個函數只有在依賴項發生變化時才會重新計算,否則會直接返回上一次計算的結果。
useCallback是對傳過來的回調函數優化,返回的是一個函數;useMemo返回值可以是任何,函數,對象等都可以。
簡單來說就是返回一個函數,只有在依賴項發生變化的時候才會更新(返回一個新的函數)。
2.案例分析:
父組件定義一個請求函數fetchData,和一個狀態query,將query當作fetchData的參數,將該函數傳遞進子組件,當父組件query發生變化時,讓子組件調用該函數發起請求。
class Parent extends Component {
state = {
query: 'react'
};
fetchData = () => {
const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query; // ... Fetch data and do something ... }; render() {
return <Child fetchData={this.fetchData} />;
}
}
class Child extends Component {
state = {
data: null
};
componentDidMount() {
this.props.fetchData();
}
componentDidUpdate(prevProps) {
// ?? This condition will never be true
if (this.props.fetchData !== prevProps.fetchData) {
this.props.fetchData();
}
}
render() {
// ...
}
}
在本代碼中,fetchData是一個class方法!(或者你也可以說是class屬性)它不會因為狀態的改變而不同,所以this.props.fetchData和 prevProps.fetchData始終相等,因此不會重新請求。
2.1舊思維–優化該案例:
子組件使用:
componentDidUpdate(prevProps) {
this.props.fetchData();
}
這樣可以發起請求,但是會在每次渲染后都去請求。
或者改變父組件:
render() {
return <Child fetchData={this.fetchData.bind(this, this.state.query)} />;
}
但這樣一來,this.props.fetchData !== prevProps.fetchData 表達式永遠是true,即使query并未改變。這會導致我們總是去請求。(bind() 方法會創建一個新的函數對象)
唯一現實可行的辦法是把query本身傳入 Child 組件。 Child 雖然實際并沒有直接使用這個query的值,但能在它改變的時候觸發一次重新請求:
class Parent extends Component {
state = {
query: 'react'
};
fetchData = () => {
const url = 'https://hn.algolia.com/api/v1/search?query=' + this.state.query;
// ... Fetch data and do something ...
};
render() {
return <Child fetchData={this.fetchData} query={this.state.query} />; }
}
class Child extends Component {
state = {
data: null
};
componentDidMount() {
this.props.fetchData();
}
componentDidUpdate(prevProps) {
if (this.props.query !== prevProps.query) { this.props.fetchData(); } }
render() {
// ...
}
}
在class組件中,函數屬性本身并不是數據流的一部分。使用useCallback,函數完全可以參與到數據流中。我們可以說如果一個函數的輸入改變了,這個函數就改變了。如果沒有,函數也不會改變。
2.2開始使用hooks:
場景一: 使用函數組件
但父組件不使用useCallback處理函數
import React, { useCallback, useState, useEffect } from 'react';
import './App.css';
function App() {
const [query, setQuery] = useState(1);
const [queryOther, setQueryOther] = useState(1);
const fecthData = () => {
console.log('新的fetch');
return query;
}
const add = () => {
console.log('點擊add');
setQuery(query + 1);
}
const addOther = () => {
console.log('點擊addOther');
setQueryOther(queryOther + 1);
}
return (
<>
<Child fecthData={fecthData} />
<button onClick={add}>+1</button>
<button onClick={addOther}>other+1</button>
<div>{ query }</div>
</>
);
}
function Child({ fecthData }: { fecthData: any }) {
console.log('子組件相關內容');
useEffect(() => {
const querN = fecthData();
console.log('子組件調用該函數獲取到相關內容', querN);
}, [fecthData])
return <div>
123
</div>
}
export default App;
初始化的時候:
點擊按鈕:
但是從圖里面可以看到,點擊addOther時,并沒有使得query發生變化,但是子組件仍然調用了該函數發起請求。可以看到這種方法需求可以使得子組件在父組件的狀態query發生變化時,成功發起了請求,但是還是存在副作用。
問題的原因在于狀態queryOther的改變,使得父組件重新渲染,重新生成了fecthData函數,并返回了該函數新的地址,導致子組件刷新。
場景二:父組件使用useCallback處理函數
import React, { useCallback, useState, useEffect } from 'react';
import './App.css';
function App() {
const [query, setQuery] = useState(1);
const [queryOther, setQueryOther] = useState(1);
const fecthData = useCallback(() => {
console.log('新的fetch');
return query;
}, [query])
const add = () => {
console.log('點擊add');
setQuery(query + 1);
}
const addOther = () => {
console.log('點擊addOther');
setQueryOther(queryOther + 1);
}
return (
<>
<Child fecthData={fecthData} />
<button onClick={add}>+1</button>
<button onClick={addOther}>other+1</button>
<div>{ query }</div>
</>
);
}
function Child({ fecthData }: { fecthData: any }) {
console.log('子組件相關內容');
useEffect(() => {
const querN = fecthData();
console.log('子組件調用該函數獲取到相關內容', querN);
}, [fecthData])
return <div>
123
</div>
}
export default App;
初始狀態
點擊按鈕:
可以看到只有點擊+1按鈕改變query才會使得子組件發起請求,點擊other+1已經沒有處罰上文副作用。
原因分析:
使用了useCallback,useCallback的工作原理是什么?useCallBack的本質工作不是在依賴不變的情況下阻止函數創建,而是在依賴不變的情況下不返回新的函數地址而返回舊的函數地址。不論是否使用useCallBack都無法阻止組件render時函數的重新創建。在本例子中點擊按鈕other+1并沒有使得query發生變化,所以并沒有返回新的fetchData函數地址,又因為在子組件中使用useEffect對fetchData監聽時,所以子組件不會發起請求。但是,點擊按鈕other+1時,子組件雖然沒發起請求,但是還是刷新了,這是什么原因呢?這是因為子組件直接在父組件中掛載,沒有做過任何優化,當父組件重新渲染時,會導致子組件也跟著渲染。所以單純的使用useCallback可以監聽到相應變化,使得子組件做出變化,但是并不能優化性能。所以當我們不用監聽某個狀態使得函數發生改變時,不要輕易使用useCallback,因為使用 useCallBack后每次執行到這里內部比對是否變化,還有存一下之前的函數,消耗更大了。
場景三:優化上述問題,搭配React.memo使用
import React, { useCallback, useState, useEffect } from 'react';
import './App.css';
function App() {
const [query, setQuery] = useState(1);
const [queryOther, setQueryOther] = useState(1);
const fecthData = useCallback(() => {
console.log('新的fetch');
return query;
}, [query])
const add = () => {
console.log('點擊add');
setQuery(query + 1);
}
const addOther = () => {
console.log('點擊addOther');
setQueryOther(queryOther + 1);
}
return (
<>
<Child fecthData={fecthData} />
<button onClick={add}>+1</button>
<button onClick={addOther}>other+1</button>
<div>{ query }</div> ,,m
const Child = React.memo(({ fecthData }: { fecthData: any }) => {
console.log('子組件相關內容');
useEffect(() => {
const querN = fecthData();
console.log('子組件調用該函數獲取到相關內容', querN);
}, [fecthData])
return <div>
123
</div>
})
export default App;
初始狀態:
點擊按鈕:
一切問題都解決了。點擊other+1按鈕,沒有使得子組件發起請求,也沒有使得子組件因為這個無關變量的變化,導致重新渲染。
原因分析:
- 使用useCallback使得無關變量變化時,阻止了新創建的fetchData的新地址返回,傳給子組件的還是原本的函數地址(useCallBack的本質工作不是在依賴不變的情況下阻止函數創建,而是在依賴不變的情況下不返回新的函數地址而返回舊的函數地址)
- React.memo 這個方法,此方法內會對 props 做一個淺層比較,如果如果 props 沒有發生改變(useCallback的存在使得props沒變化),則不會重新渲染此組件。
場景四:單純使用React.memo會發生什么
import React, { useCallback, useState, useEffect } from 'react';
import './App.css';
function App() {
const [query, setQuery] = useState(1);
const [queryOther, setQueryOther] = useState(1);
const fecthData = () => {
console.log('新的fetch');
return query;
}
const add = () => {
console.log('點擊add');
setQuery(query + 1);
}
const addOther = () => {
console.log('點擊addOther');
setQueryOther(queryOther + 1);
}
return (
<>
<Child fecthData={fecthData} />
<button onClick={add}>+1</button>
<button onClick={addOther}>other+1</button>
<div>{ query }</div>
</>
);
}
const Child = React.memo(({ fecthData }: { fecthData: any }) => {
console.log('子組件相關內容');
useEffect(() => {
const querN = fecthData();
console.log('子組件調用該函數獲取到相關內容', querN);
}, [fecthData])
return <div>
123
</div>
})
export default App;
初始狀態:
點擊按鈕
React.memo檢測的是props中數據的棧地址是否改變。而父組件重新構建的時候,會重新構建父組件中的所有函數(舊函數銷毀,新函數創建,等于更新了函數地址),新的函數地址傳入到子組件中被props檢測到棧地址更新。也就引發了子組件的重新渲染。所以,在上面的代碼示例里面,子組件是要被重新渲染的。上文中的fetchData因為失去了useCallback的保護使得子組件的props發生了變化,從而React.memo也失去了作用,而且因為fetchData因為失去了useCallback的保護,使得點擊other+1按鈕改變無關的變量時,子組件也調用了請求函數。
3.useCallback使用總結:
- 可以使用useCallback可以監聽到相應狀態變化,使得父/子組件做出響應。
- 但是濫用useCallback會影響性能,需搭配React.memo進行使用,否則適得其反。
原文鏈接:https://blog.csdn.net/qq_44742090/article/details/131525420
- 上一篇:沒有了
- 下一篇:沒有了
相關推薦
- 2022-10-16 Qt實現串口助手_C 語言
- 2022-07-25 C/C++實現線性單鏈表的示例代碼_C 語言
- 2022-08-19 Linux系統文件目錄介紹
- 2023-03-21 解讀torch.nn.GRU的輸入及輸出示例_python
- 2022-02-17 antv g2設置chart圖例的legend為一條線與一個圓的組合
- 2022-05-27 python繪制棉棒圖的方法詳解_python
- 2021-12-31 使用go實現一個超級mini的消息隊列的示例代碼_Golang
- 2022-11-14 關于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同步修改后的遠程分支