在我理解useMemo和useCallback是负责缓存优化的。
我最开始讲到react组件有个机制:当父组件任何变动的情况下,子组件都会被重新渲染,即使prop所依赖的值没有发生变化。
在类组件中,我们通常在shouldComponentUpdate中进行判断是否更新。但是在函数组件中没有shouldComponentUpdate。这意味着函数组件的每一次调用都会执行其内部的所有逻辑,那么会带来较大的性能损耗。因此useMemo 和useCallback就是解决性能问题的杀手锏。
useMemo用法:
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
我们通过一个简单的例子来看一下:
import { useState, useMemo } from 'react';
export default function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
return (
<div>
<Child count={count} />
<h4>父组件:{count}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)} />
</div>
</div>
)
}
function Child({ coun t }: {count: any}) {
// 未使用useMemo
const time = () => {
// 这里假设执行了一堆代码
console.log('执行了一大堆代码');
return new Date().getTime()
}
// 使用useMemo时
// const time = useMemo(()=>{
// console.log('执行了一大堆代码');
// return new Date().getTime()
// },[count])
return (
<div>
<h4>子组件:{count}</h4>
<p>{time()}</p>
<hr />
</div>
)
}
正常情况下,点击按钮和在输入框输入时,都会重新渲染子组件。
当我们想只在点击按钮时渲染子组件,输入框输入时不渲染子组件,我们就可以使用useMemo,并添加上相应的依赖就可以了。
接下来我们再实现一个简单的useMemo:
let hookStates: any = [];
let hookIndex: number = 0;
function useMemos(nextCreate: () => any, dependencies: any[]) {
if (hookStates[hookIndex]) { // 说明不是第一次渲染
let [lastMemo, lastDependencies] = hookStates[hookIndex];
let same = dependencies.every((item, index) => item === lastDependencies);
if (same) {
hookIndex++;
return lastMemo;
}
}
const nextValue = nextCreate(); // 此处有点不用
// 第一次渲染 或者 不是第一次但是依赖项相同,都返回新的
hookStates[hookIndex++] = [nextValue, dependencies];
return nextValue;
}
useCallback
useMemo和useCallback都会在组件第一次渲染的时候执行,之后会在其依赖的变量发生改变时再次执行;并且这两个hooks都返回缓存的值。二者的区别是useMemo返回缓存的变量,useCallback返回缓存的函数。
用法:useCallback(fn, deps) === useMemo(() => fn, deps)
上面的useCallback会将我们传递给它的函数fnB返回,并且将这个结果缓存;当依赖a变更时,会返回新的函数。
我们通过举个例子可以看一下。我们可以借助Set来检验一下返回的时候是一个新函数。
import { useState, useCallback, useEffect } from 'react';
const set = new Set();
export default function Parent() {
const [count, setCount] = useState(1);
const [val, setVal] = useState('');
// count改变,会返回一个新的函数
// val改变,会返回之前缓存的函数
const callback = useCallback(() => {
return new Date().getTime();
console.log('假设执行了一大堆代码');
}, [count]);
// const callback = () => {
// return new Date().getTime();
// console.log('假设执行了一大堆代码');
// }
// 借助set来判断返回的函数是否为新函数
set.add(callback);
return <div>
<h4>父组件:{count}</h4>
<h4>set:{set.size}</h4>
<Child callback={callback} />
<div>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={val} onChange={event => setVal(event.target.value)} />
</div>
</div>;
}
function Child({ callback }:{callback: any}) {
const [count, setCount] = useState(() => callback());
useEffect(() => {
setCount(callback());
}, [callback]);
return <div>
子组件:{count}
</div>
}
我们再实现一个简单的useCallback
let hookStates: any = [];
let hookIndex: number = 0;
function useCallbacks(callback: any, dependencies: any) {
if (hookStates[hookIndex]) { // 说明不是第一次渲染
let [lastCallback, lastDependencies] = hookStates[hookIndex];
let same = dependencies.every((item: any, index: any) => item === lastDependencies);
if (same) {
hookIndex++;
return lastCallback;
}
}
// 第一次渲染 或者 不是第一次但是依赖项相同,都返回新的
hookStates[hookIndex++] = [callback, dependencies];
return callback
}