React缓存,useCallback钩子如何保持函数不变?配合memo
发布于 作者:苏南大叔 来源:程序如此灵动~useCallback
缓存的是函数,意思是:无论组件如何刷新重新渲染,定义在组件内部的被useCallback
的函数(的内存指向)都不变。和专门缓存组件的高阶函数memo()
是完美搭配的关系。memo()
负责缓存组件,组件传递的props
里面的函数定义,由useCallback
进行处理,以保持引用不变。当然,一些钩子的导出函数(useState()
的导出等)本来就是不变的,并不需要useCallback
进行处理。
苏南大叔的“程序如此灵动”博客,记录苏南大叔的代码编程经验总结。本文测试环境:nodejs@20.18.0
,create-react-app@5.0.1
,react-router-dom@6.27.0
,react@18.3.1
。useCallback()
是个钩子,所以只能用在函数式组件里面。本文的第一个代码可以表明:useCallback
不仅可以作用于组件内部的自定义函数上,还可以作用于普通变量,数组甚至对象上。
判断函数/变量是否改变
毕竟内存引用地址这个东西,是看不见摸不着的。用来解释原理,可以。但是,总是有些牵强。这里使用一段代码来更加直观的解释这个问题。
import React, { useCallback, useState } from "react";
const set_var = new Set();
const set_var2 = new Set();
const set_arr = new Set();
const set_arr2 = new Set();
const set_obj = new Set();
const set_obj2 = new Set();
const set_fn1 = new Set();
const set_fn2 = new Set();
export default function App() {
const [a, setA] = useState(1);
let var1 = "变量";
let var2 = useCallback(var1, []);
set_var.add(var1);
set_var2.add(var2);
let arr1 = [1];
let arr2 = useCallback(arr1, []);
set_arr.add(arr1);
set_arr2.add(arr2);
let obj1 = {"s":"n"};
let obj2 = useCallback(obj1, []);
set_obj.add(obj1);
set_obj2.add(obj2);
let fn1 = () => {};
let fn2 = useCallback(fn1, []);
set_fn1.add(fn1);
set_fn2.add(fn2);
return (
<div>
<button onClick={() => setA(a + 1)}>修改a:{a},引发父级渲染</button>
<br />
变量:{set_var.size},{set_var2.size},{Array.from(set_var2).join(',')}
<br />
数组:{set_arr.size},{set_arr2.size},{Array.from(set_arr2).join(',')}
<br />
对象:{set_obj.size},{set_obj2.size},{Array.from(set_obj2).map((x)=>JSON.stringify(x))}
<br />
函数:{set_fn1.size},{set_fn2.size}
<br />
</div>
);
}
这里的原理就是:set()
天然去重,如果每次组件渲染时的变量或者函数,被添加到set()
里面后,被接受了,那么就证明已经不是同一个变量/函数了。
这个例子,还说明了一个问题。如果一个函数或者变量与状态无关,那么,它就没有什么必要非要定义在组件函数体里面。提升一级,反而是更好的选择,节约下内存。
useCallback 测试代码
其实,useCallback()
钩子的原理,已经在本文开头说的很明白了。记住:useCallback()
保持的是函数(的内存地址)不变,函数体本身没有被执行。useMemo()
保持的是函数的运算结果不变,函数体被执行了。
import React, { useCallback, useState, useMemo } from "react";
function Child({ alt }) {
// console.log('Child')
return (
<div>
{alt}, {Date.now().toString().slice(-6)}
</div>
);
}
const MemoChild = React.memo(Child);
export default function App() {
console.log("app");
const [a, setA] = useState(1);
let fn = () => {
console.log("run", a);
return a + 1;
};
let fn2 = useCallback(fn, []); // 这个返回的是函数,没被执行
let result = useMemo(fn, []); // 这个是返回的是结果,被执行了
let result2 = useMemo(fn, [a]); // 这个是返回的是结果,被执行了,a变它就变
return (
<div>
<button onClick={() => setA(a + 1)}>修改a:{a},引发父级渲染</button>
<h4>useMemo(依赖项):</h4>
恒定不变{result}, a变它就变{result2}
<br />
<h4>memo+useCallback(函数props):</h4>
<Child alt="恒定变化" />
<MemoChild fn={fn} alt="恒定变化" />
<MemoChild fn={setA} alt="恒定不变" />
<MemoChild fn={fn2} alt="恒定不变" />
</div>
);
}
代码很简单,不解释。只要就是对比阐述useMemo()
和useCallback()
的区别。
相关文章
- https://newsn.net/say/react-memo.html
- https://newsn.net/say/react-memo-2.html
- https://newsn.net/say/react-memo-3.html
- https://newsn.net/say/react-usememo.html
- https://newsn.net/say/react-usememo-2.html
- https://newsn.net/say/react-useasyncmemo.html
- https://newsn.net/say/react-usecallback.html
本博客不欢迎:各种镜像采集行为。请尊重原创文章内容,转载请保留作者链接。