React Hooks之使用useCallback和useMemo進行性能優(yōu)化方式
useCallback和useMemo性能優(yōu)化
useCallback的解析
useCallback的使用
useCallback實際的目的是為了進行性能的優(yōu)化。
useCallback進行什么樣的優(yōu)化呢?
例如下面這個計數器的案例, 我們點擊按鈕時, counter數據會發(fā)生變化, App函數組件就會重新渲染, 意味著increment函數就會被重新定義一次, 每點擊一次按鈕, increment函數就會重新被定義;
雖然每次定義increment函數, 垃圾回收機制會將上一次定義的increment函數回收, 但是這種不必要的重復定義是會影響性能的
import React, { memo, useState } from 'react'
const App = memo(() => {
? const [counter, setCounter] = useState(10)
? function increment() {
? ? setCounter(counter + 1)
? }
? return (
? ? <div>
? ? ? <h2>{counter}</h2>
? ? ? <button onClick={() => increment()}>+1</button>
? ? </div>
? )
})
export default App如何進行性能的優(yōu)化呢?
調用useCallback會返回一個 memoized(有記憶的) 的回調函數;
在依賴不變的情況下,多次定義的時候,返回的回調函數是相同的;
- 參數一: 傳入一個回調函數, 如果依賴發(fā)生改變會定義一個新的該回調函數使用, 如果依賴沒有發(fā)生改變, 依然使用原來的回調函數
- 參數二: 用于控制依賴的, 第二個參數要求傳入一個數組, 數組中可以傳入依賴, 傳空數組表示沒有依賴
const memoizedCallback = useCallback(
? () => {
? ?doSomething(a, b)
? },
? [a, b]
)useCallback拿到的結果是函數
useCallback的作用
通常使用useCallback的目的是在向子組件傳遞函數時, 將要傳遞的函數進行優(yōu)化在傳遞給子組件, 避免子組件進行多次渲染;
并不是為了函數不再重新定義, 也不是對函數定義做優(yōu)化
我們來看下面這樣一個案例:
定義一個子組件Test, 并將increment函數傳遞到子組件中, 我們在子組件中可以拿到increment方法修改App組件中的counter;
由于counter發(fā)生改變, 就會重新定義一個新的increment函數, 因此我們只要修改了counter, 就會傳遞一個新的increment函數到Test組件中; Test組件中的props就會發(fā)生變化, Test組件會被重新渲染
import React, { memo, useState, useCallback } from 'react'
const Test = memo((props) => {
? console.log("Test組件被重新渲染")
? return (
? ? <div>
? ? ? <button onClick={props.increment}>Test+1</button>
? ? </div>
? )
})
const App = memo(() => {
? const [counter, setCounter] = useState(10)
? function increment() {
? ? setCounter(counter + 1)
? }
? return (
? ? <div>
? ? ? <h2>{counter}</h2>
? ? ? <button onClick={increment}>+1</button>
? ? ? <Test increment={increment}/>
? ? </div>
? )
})
export default App如果此時App組件中再定義一個方法changeMessage用來修改message;
我們會發(fā)現當message發(fā)生改變時, 子組件Test也會被重新渲染; 這是因為message發(fā)生改變, App組件會重新渲染, 那么就會重新定義一個新的increment函數, 將新的increment函數傳遞到Test組件, Test組件的props發(fā)生改變就會重新渲染
import React, { memo, useState, useCallback } from 'react'
const Test = memo((props) => {
? console.log("Test組件被重新渲染")
? return (
? ? <div>
? ? ? <button onClick={props.increment}>Test+1</button>
? ? </div>
? )
})
const App = memo(() => {
? const [counter, setCounter] = useState(10)
? const [message, setMessage] = useState("哈哈哈哈")
? function increment() {
? ? setCounter(counter + 1)
? }
? return (
? ? <div>
? ? ? <h2>{counter}</h2>
? ? ? <button onClick={increment}>+1</button>
? ? ? <h2>{message}</h2>
? ? ? <button onClick={() => setMessage("呵呵呵呵")}>修改message</button>
? ? ? <Test increment={increment}/>
? ? </div>
? )
})
export default App但是如果我們使用useCallback, 就可以避免App組件中message發(fā)生改變時, Test組件重新渲染
因為message組件發(fā)生改變, 但是我們下面的useCallback函數是依賴counter的, 在依賴沒有發(fā)生改變時, 多次定義返回的值是相同的(也就是修改message重新渲染App組件時, increment并沒有重新定義, 依然是之前的); 就意味著Test組件中的props沒有改變, 因此Test組件不會被重新渲染
如果是counter值發(fā)生改變, 因為useCallback函數是依賴counter的, 所以會定義一個新的函數給increment; 當向Test組件傳遞新的increment時, Test組件的props就會改變, Test依然會重新渲染, 這也是我們想要實現的效果
import React, { memo, useState, useCallback } from 'react'
const Test = memo((props) => {
? console.log("Test組件被重新渲染")
? return (
? ? <div>
? ? ? <button onClick={props.increment}>Test+1</button>
? ? </div>
? )
})
const App = memo(() => {
? const [counter, setCounter] = useState(10)
? const [message, setMessage] = useState("哈哈哈哈")
? // 使用useCallback依賴于counter
? const increment = useCallback(() => {
? ? setCounter(counter + 1)
? }, [counter])
? return (
? ? <div>
? ? ? <h2>{counter}</h2>
? ? ? <button onClick={increment}>+1</button>
? ? ? <h2>{message}</h2>
? ? ? <button onClick={() => setMessage("呵呵呵呵")}>修改message</button>
? ? ? <Test increment={increment}/>
? ? </div>
? )
})
export default App還可以再進一步的進行優(yōu)化:
現在我們的代碼是counter發(fā)生變化時, useCallback會重新定義一個新的函數返回給increment; 但是我們想做到, counter發(fā)生變化, 依然使用原來的函數, 不需要重新定義一個新的函數;
可能會有小伙伴想, 直接將依賴改為一個空數組, 但是如果是這樣的話就會產生閉包陷阱;
我們修改counter時確實不會重新生成一個新的函數, 但是原來的函數中使用的counter永遠是之前的值, 也就是0;
這是因為我們舊的函數在定義的那一刻, counter的值是0;
由于修改counter依然使用舊的函數, 這樣無論我們修改多少次counter, 頁面展示的數據永遠是 0 + 1 的結果
const increment = useCallback(() => {
? setCounter(counter + 1)
}, [])這個時候我們就需要結合使用另一個hook: useRef
useRef函數在組件多次進行渲染時, 返回的是同一個值;
我們就可以將最新的counter儲存到useRef返回的對象的current屬性中;
這樣做的好處就是, counter發(fā)生改變時, 也不會重新定義一個函數, 意味著修改counter也不會導致Test組件重新渲染
import React, { memo, useState, useCallback, useRef } from 'react'
const Test = memo((props) => {
? console.log("Test組件被重新渲染")
? return (
? ? <div>
? ? ? <button onClick={props.increment}>Test+1</button>
? ? </div>
? )
})
const App = memo(() => {
? const [counter, setCounter] = useState(10)
? const [message, setMessage] = useState("哈哈哈哈")
? // 組件進行多次渲染, 返回的是同一個ref對象
? const counterRef = useRef()
? // 將最新的counter保存到ref對象current屬性中
? counterRef.current = counter
? const increment = useCallback(() => {
? ? // 在修改數據時, 引用保存到ref對象current屬性的最新的值
? ? setCounter(counterRef.current + 1)
? }, [])
? return (
? ? <div>
? ? ? <h2>{counter}</h2>
? ? ? <button onClick={increment}>+1</button>
? ? ? <Test increment={increment}/>
? ? ? <h2>{message}</h2>
? ? ? <button onClick={() => setMessage("呵呵呵呵")}>修改message</button>
? ? </div>
? )
})
export default AppuseMemo的解析
useMemo實際的目的也是為了進行性能的優(yōu)化, 例如下面這個例子
我們定義一個計算累加的函數calcNumTotal, 在App組件中調用這個函數計算結果
但是counter改變時, App組件就會重新渲染, 那么calcNumTotal函數又會重新計算; 但是counter的改變和calcNumTotal函數并沒有關系, 卻要重新渲染; 這種類似的場景我們就可以使用useMemo進行性能優(yōu)化
import React, { memo } from 'react'
import { useState } from 'react'
// 定義一個函數求和
function calcNumTotal(num) {
? let total = 0
? for (let i = 1; i <= num; i++) {
? ? total += i
? }
? return total
}
const App = memo(() => {
? const [counter, setCounter] = useState(10)
? return (
? ? <div>
? ? ? {/* couter改變, 組件重新渲染, 意味著calcNumTotal函數也會重新執(zhí)行, 重新計算結果 */}
? ? ? <h2>計算結果: {calcNumTotal(100)}</h2>
? ? ? <h2>當前計數: {counter}</h2>
? ? ? <button onClick={() => setCounter(counter + 1)}>+1</button>
? ? </div>
? )
})
export default App如何使用 useMemo進行性能的優(yōu)化呢?
useMemo返回的也是一個 memoized(有記憶的) 值; 在依賴不變的情況下,多次定義的時候,返回的值是相同的;
- 參數一: 傳入一個回調函數
- 參數二: 傳入一個數組, 表示依賴, 什么都不依賴傳入空數組; 如果不傳則該函數什么都不會做, 無意義
const memoizedValue = useMemo(
? () => {
? ? computeExpensiveValue(a, b)
? },?
? [a, b]
)這樣我們就可以對上面的代碼進行優(yōu)化了, 實現counter發(fā)生變化, 而calcNumTotal函數不需要重新計算結果
import React, { memo, useMemo, useState } from 'react'
// 定義一個函數求和
function calcNumTotal(num) {
? console.log("calcNumTotal函數被調用")
? let total = 0
? for (let i = 1; i <= num; i++) {
? ? total += i
? }
? return total
}
const App = memo(() => {
? const [counter, setCounter] = useState(10)
? let result = useMemo(() => {
? ? return calcNumTotal(50)
? }, [])
? return (
? ? <div>
? ? ? {/* couter改變, 組件重新渲染, 意味著calcNumTotal函數也會重新執(zhí)行, 重新計算結果 */}
? ? ? <h2>計算結果: {result}</h2>
? ? ? <h2>當前計數: {counter}</h2>
? ? ? <button onClick={() => setCounter(counter + 1)}>+1</button>
? ? </div>
? )
})
export default AppuseMemo與useCallback的區(qū)別:
useMemo拿到的傳入回調函數的返回值, useCallback拿到的傳入的回調函數本身;
簡單來說useMemo是對函數的返回值做優(yōu)化, useCallback是對函數做優(yōu)化;
useCallback(fn, [])和uesMemo(() => fn, [])表達的是同一個意思
總結
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
- 詳解React中的useMemo和useCallback的區(qū)別
- 深入React?18源碼useMemo?useCallback?memo用法及區(qū)別分析
- React中memo useCallback useMemo方法作用及使用場景
- React中useCallback useMemo使用方法快速精通
- React中useCallback useMemo到底該怎么用
- React源碼分析之useCallback與useMemo及useContext詳解
- 解析React中useMemo與useCallback的區(qū)別
- react性能優(yōu)化useMemo與useCallback使用對比詳解
- React?正確使用useCallback?useMemo的方式
- React中useMemo、useCallback的具體使用
相關文章
解決React報錯Property value does not exist&n
這篇文章主要為大家介紹了React報錯Property value does not exist on type HTMLElement解決方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-12-12
React?錯誤邊界Error?Boundary使用示例解析
這篇文章主要為大家介紹了React?錯誤邊界Error?Boundary使用示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-09-09
詳解react-native WebView 返回處理(非回調方法可解決)
這篇文章主要介紹了詳解react-native WebView 返回處理(非回調方法可解決),小編覺得挺不錯的,現在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-02-02

