useCallback 与 useMemo优化总结
是否能每一个方法都包裹一下useCallback?
答案是: 不要把所有的方法都包上 useCallback
// useCallback 用与缓存函数,但不可滥用!
const [count1, setCount1] = useState(0);
const [count2, setCount2] = useState(0);
const handleClickButton1 = () => {
setCount1(count1 + 1)
};
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1)
}, [count2]);
return (
<>
<button onClick={handleClickButton1}>button1</button>
<button onClick={handleClickButton2}>button2</button>
</>
)
/* 组件重新渲染时会重新创建 handleClickButton1 函数,
&
* handleClickButton2 里的 useCallback里也会重新创建一个函数,
&
* 尽管这个函数可能因为依赖值一样,不会被赋予handleClickButton2变量上。
&
&
* 这种情况下,handleClickButton2会取之前的函数,
&
* 但因为下面的button会被重新渲染,到handleClickButton2这里,
&
* useCallback 每次都需要对比 **依赖值是否变化,在加上还要缓存之前的值,
&
* 性能开销反而更大。
&
* 这种情况建议不要再套一层 useCallback
*/
useCallback 传空数组(不更新带来的问题)
// 传空数组意味着当前方法没有依赖值,将不会被更新,
// 另外因为js **静态作用域 将导致此函数内部count2永远都是0 不太懂,什么是静态作用域
import React, { useState, useCallback } from 'react';
import Button from './Button';
export default function App() {
const [count2, setCount2] = useState(0);
const handleClickButton2 = useCallback(() => {
setCount2(count2 + 1);
}, []);
return (
<Button
count={count2}
onClickButton={handleClickButton2}
>Button2</Button>
);
}
// 由点击button随机数发生的变化判断,Button只被重渲了一次
// Button.jsx
import React from 'react';
const Button = ({ onClickButton, children }) => {
return (
<>
<button onClick={onClickButton}>{children}</button>
<span>{Math.random()}</span>
</>
);
};
export default React.memo(Button);
useCallback 频繁更新带来的问题(使用useRef优化)
// 这里 handleSubmit 的缓存与否取决于 text,
// 作为input 的value, text更新必然是相当频繁的,
// 带来的问题就是,handleSubmit所对应的函数的频繁更新,
// 导致了 OtherForm的频繁渲染
const [text, setText] = useState('');
const handleSubmit = useCallback(() => {
// ...
}, [text]);
return (
<form>
<input value={text} onChange={(e) => setText(e.target.value)} />
<OtherForm onSubmit={handleSubmit} />
</form>
);
/**
* useRef生成的对象在整个组件保持不变(引用不变),
&
* 且在组件的任何地方都可以访问到最新的值,
&
* 这里 textRef 由于引用不变, handleSubmit只会被创建一次,
&
* 因为这个原因,OtherForm 也不会频繁渲染,节省了性能。
&
* 虽然函数只被创建了一次,但useRef对象保存的值在此函数是可以实时拿到的? ****待定
*/
const textRef = useRef('');
const [text, setText] = useState('');
const handleSubmit = useCallback(() => {
console.log(textRef.current);
// ...
}, [textRef]);
return (
<form>
<input value={text} onChange={(e) => {
const { value } = e.target;
setText(value)
textRef.current = value;
}} />
<OtherForm onSubmit={handleSubmit} />
</form>
);
useCallback 一般使用最多场景
useCallback 是要配合子组件的 shouldComponentUpdate 或者 React.memo 一起来使用的,否则就是反向优化。
useMemo (使用场景广泛)
优化子组件
const [count, setCount] = useState(0);
const userInfo = {
// ...
age: count,
name: 'Jace'
}
return <UserCard userInfo={userInfo}>
/**
* 这里的 userInfo 每次都是一个新对象,无论count发生改变没有,
* 都会导致子组件 UserCard 重新渲染
*/
const userInfo = useMemo(() => {
return {
// ...
name: "Jace",
age: count
};
}, [count]);
/**
* 这里的 userInfo 会在count 发生变化才返回新对象
*/
优化自身
在组件内部有一些复杂的计算时,可以使用useMemo缓存这个值。
useMemo 更多的使用场景
// 可将useMemo 的返回值 定义为一个数组,这样就可以通过结构赋值的方式取值。
// 若返回值数组的其中一项包含数组,那么也就变相的实现了useCallback,
// 同时还实现了对多个数据的缓存。
const [age, followUser] = u
new Date().getFullYear() - userInfo.birth, // 根据生日计算年龄
async () => { // 关注用户
await request('/follow', { uid: userInfo.id });
// ...
}
];
}, [userInfo]);
return (
<div>
<span>name: {userInfo.name}</span>
<span>age: {age}</span>
<Card followUser={followUser}/>
// **更加新奇的用法!
{
useMemo(() => (
// 如果 Card1 组件内部没有使用 React.memo 函数,那还可以通过这种方式在父组件减少子组件的渲染
<Card1 followUser={followUser}/>
), [followUser])
}
</div>
)