文章目录
useCallback和useMemo性能优化
useCallback的解析
useCallback的使用
useCallback实际的目的是为了进行性能的优化。
useCallback进行什么样的优化呢?
例如下面这个计数器的案例, 我们点击按钮时, counter数据会发生变化, App函数组件就会重新渲染, 意味着increment函数就会
被重新定义一次
, 每点击一次按钮, increment函数就会重新被定义; 虽然每次定义increment函数, 垃圾回收机制会将上一次定义的increment函数回收, 但是这种不必要的重复定义是会影响性能的
import React,{ memo, useState }from'react'const App =memo(()=>{const[counter, setCounter]=useState(10)functionincrement(){setCounter(counter +1)}return(<div><h2>{counter}</h2><button onClick={()=>increment()}>+1</button></div>)})exportdefault App
如何进行性能的优化呢?
调用useCallback会返回一个 memoized(有记忆的) 的回调函数;
在依赖不变的情况下,多次定义的时候,返回的回调函数是相同的
;
参数一: 传入一个回调函数, 如果依赖发生改变会定义一个新的该回调函数使用, 如果依赖没有发生改变, 依然使用原来的回调函数
参数二: 用于控制依赖的, 第二个参数要求传入一个数组, 数组中可以传入依赖, 传空数组表示没有依赖
const memoizedCallback =useCallback(()=>{doSomething(a, b)},[a, b])
useCallback拿到的结果是函数
useCallback的作用
通常使用useCallback的目的是在向子组件传递函数时, 将要传递的函数进行优化在传递给子组件, 避免子组件进行多次渲染;
并不是为了函数不再重新定义, 也不是对函数定义做优化
我们来看下面这样一个案例;
定义一个子组件Test, 并将increment函数传递到子组件中, 我们在子组件中可以拿到increment方法修改App组件中的counter;
由于counter发生改变, 就会重新定义一个新的increment函数, 因此我们只要修改了counter, 就会传递一个新的increment函数到Test组件中; 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)functionincrement(){setCounter(counter +1)}return(<div><h2>{counter}</h2><button onClick={increment}>+1</button><Test increment={increment}/></div>)})exportdefault App
如果此时App组件中再定义一个方法changeMessage用来修改message;
我们会发现当message发生改变时, 子组件Test也会被重新渲染; 这是因为message发生改变, App组件会重新渲染, 那么就会重新定义一个新的increment函数, 将新的increment函数传递到Test组件, Test组件的props发生改变就会重新渲染
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("哈哈哈哈")functionincrement(){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>)})exportdefault App
但是如果我们使用useCallback, 就可以避免App组件中message发生改变时, Test组件重新渲染
因为message组件发生改变, 但是我们下面的useCallback函数是依赖counter的, 在依赖没有发生改变时, 多次定义返回的值是相同的(也就是修改message重新渲染App组件时, increment并没有重新定义, 依然是之前的); 就意味着Test组件中的props没有改变, 因此Test组件不会被重新渲染
如果是counter值发生改变, 因为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依赖于counterconst 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>)})exportdefault App
还可以再进一步的进行优化:
现在我们的代码是counter发生变化时, useCallback会重新定义一个新的函数返回给increment; 但是我们想做到, counter发生变化, 依然使用原来的函数, 不需要重新定义一个新的函数;
可能会有小伙伴想, 直接将依赖改为一个空数组, 但是如果是这样的话就会产生
闭包陷阱
; 我们修改counter时确实不会重新生成一个新的函数, 但是原来的函数中使用的counter永远是之前的值, 也就是0; 这是因为我们旧的函数在定义的那一刻, counter的值是0; 由于修改counter依然使用旧的函数, 这样无论我们修改多少次counter, 页面展示的数据永远是 0 + 1 的结果
const increment =useCallback(()=>{setCounter(counter +1)},[])
这个时候我们就需要结合使用另一个hook: useRef
useRef函数在组件多次进行渲染时, 返回的是同一个值; 我们就可以将最新的counter储存到useRef返回的对象的current属性中; 这样做的好处就是, counter发生改变时, 也不会重新定义一个函数, 意味着修改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>)})exportdefault App
useMemo的解析
useMemo实际的目的也是为了进行性能的优化, 例如下面这个例子
我们定义一个计算累加的函数calcNumTotal, 在App组件中调用这个函数计算结果
但是counter改变时, App组件就会重新渲染, 那么calcNumTotal函数又会重新计算; 但是counter的改变和calcNumTotal函数并没有关系, 却要重新渲染; 这种类似的场景我们就可以使用useMemo进行性能优化
import React,{ memo }from'react'import{ useState }from'react'// 定义一个函数求和functioncalcNumTotal(num){let total =0for(let i =1; i <= num; i++){
total += i
}return total
}const App =memo(()=>{const[counter, setCounter]=useState(10)return(<div>{/* couter改变, 组件重新渲染, 意味着calcNumTotal函数也会重新执行, 重新计算结果 */}<h2>计算结果:{calcNumTotal(100)}</h2><h2>当前计数:{counter}</h2><button onClick={()=>setCounter(counter +1)}>+1</button></div>)})exportdefault App
如何使用 useMemo进行性能的优化呢?
useMemo返回的也是一个 memoized(有记忆的) 值; 在依赖不变的情况下,多次定义的时候,返回的值是相同的;
参数一: 传入一个回调函数
参数二: 传入一个数组, 表示依赖, 什么都不依赖传入空数组; 如果不传则该函数什么都不会做, 无意义
const memoizedValue =useMemo(()=>{computeExpensiveValue(a, b)},[a, b])
这样我们就可以对上面的代码进行优化了, 实现counter发生变化, 而calcNumTotal函数不需要重新计算结果
import React,{ memo, useMemo, useState }from'react'// 定义一个函数求和functioncalcNumTotal(num){
console.log("calcNumTotal函数被调用")let total =0for(let i =1; i <= num; i++){
total += i
}return total
}const App =memo(()=>{const[counter, setCounter]=useState(10)let result =useMemo(()=>{returncalcNumTotal(50)},[])return(<div>{/* couter改变, 组件重新渲染, 意味着calcNumTotal函数也会重新执行, 重新计算结果 */}<h2>计算结果:{result}</h2><h2>当前计数:{counter}</h2><button onClick={()=>setCounter(counter +1)}>+1</button></div>)})exportdefault App
**useMemo与useCallback的
区别
**:
useMemo拿到的传入回调函数的返回值, useCallback拿到的传入的回调函数本身;
简单来说useMemo是对函数的返回值做优化, useCallback是对函数做优化;
useCallback(fn, [])
和
uesMemo(() => fn, [])
表达的是同一个意思
版权归原作者 学全栈的灌汤包 所有, 如有侵权,请联系我们删除。