转载自 https://blog.csdn.net/MFWSCQ/article/details/105136711
useCallback 可以理解为 useMemo的语法糖。 useCallback((x) => { log(x) }, [m]) 等价于 useMemo(() => { (x) => { log(x) } }, [m])
主要区别是 React.useMemo
将调用 fn
函数并返回其结果,而 React.useCallback
将返回 fn
函数而不调用它。
useCallback
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
);
返回一个 memoized 回调函数 。
把内联回调函数及依赖项数组作为参数传入 useCallback
,它将返回该回调函数的 memoized 版本,该回调函数仅在某个依赖项改变时才会更新。当你把回调函数传递给经过优化的并使用引用相等性去避免非必要渲染(例如 shouldComponentUpdate
)的子组件时,它将非常有用。
useCallback(fn, deps) 相当于 useMemo(() => fn, deps)。
useMemo
const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
返回一个 memoized 值 。
把“创建”函数和依赖项数组作为参数传入 useMemo
,它仅会在某个依赖项改变时才重新计算 memoized 值。这种优化有助于避免在每次渲染时都进行高开销的计算。
记住,传入
useMemo
的函数会在 渲染期间 执行。请不要在这个函数内部执行与渲染无关的操作,诸如副作用这类的操作属于useEffect
的适用范畴,而不是useMemo
。
如果没有提供依赖项数组,useMemo
在每次渲染时都会计算新的值。
你可以把 useMemo
作为性能优化的手段,但不要把它当成语义上的保证。将来,React 可能会选择“遗忘”以前的一些 memoized 值,并在下次渲染时重新计算它们,比如为离屏组件释放内存。先编写在没有 useMemo
的情况下也可以执行的代码 —— 之后再在你的代码中添加 useMemo
,以达到优化性能的目的。
相同点:
- useCallback 和 useMemo 参数相同,第一个参数是函数,第二个参数是依赖项的数组。
- useMemo、useCallback 都是使参数(函数)不会因为其他不相关的参数变化而重新渲染。
- 与 useEffect 类似,[] 内可以放入你改变数值就重新渲染参数(函数)的对象。如果 [] 为空就是只渲染一次,之后都不会渲染。
区别:
- 主要区别是
React.useMemo
将调用fn
函数并返回其结果,而React.useCallback
将返回fn
函数而不调用它。
使用 useMemo 栗子:
不使用:
function Example() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
function getNum() {
return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
}
return <div>
<h4>总和:{getNum()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
上面这个组件,维护了两个 state ,可以看到 getNum
的计算仅仅跟 count
有关,但是现在无论是 count
还是 val
变化,都会导致 getNum
重新计算,所以这里我们希望 val
修改的时候,不需要再次计算,这种情况下我们可以使用 useMemo
。
使用:
function Example() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const getNum = useMemo(() => {
return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
}, [count])
return <div>
<h4>总和:{getNum()}</h4>
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
使用 useMemo
后,并将 count
作为依赖值传递进去,此时仅当 count
变化时才会重新执行 getNum
。
useCallback的使用场景:
有一个父组件,其中包含子组件,子组件接收一个函数作为 props ;通常而言,如果父组件更新了,子组件也会执行更新;但是大多数场景下,更新是没有必要的,我们可以借助 useCallback 来返回函数,然后把这个函数作为 props 传递给子组件;这样,子组件就能避免不必要的更新。
function Parent() {
const [count, setCount] = useState(1);
const [val, setValue] = useState('');
const getNum = useCallback(() => {
return Array.from({length: count * 100}, (v, i) => i).reduce((a, b) => a+b)
}, [count])
return <div>
<Child getNum={getNum} />
<div>
<button onClick={() => setCount(count + 1)}>+1</button>
<input value={val} onChange={event => setValue(event.target.value)}/>
</div>
</div>;
}
const Child = React.memo(function ({ getNum }: any) {
return <h4>总和:{getNum()}</h4>
})
使用 useCallback
之后,仅当 count
发生变化时 Child
组件才会重新渲染,而 val
变化时,Child
组件是不会重新渲染的。
useCallback使用的第二个栗子:测量 DOM 节点
function MeasureExample() {
const [height, setHeight] = useState(0);
const measuredRef = useCallback(node => { if (node !== null) { setHeight(node.getBoundingClientRect().height); } }, []);
return (
<>
<h1 ref={measuredRef}>Hello, world</h1> <h2>The above header is {Math.round(height)}px tall</h2>
</>
);
}
获取 DOM 节点的位置或是大小的基本方式是使用 callback ref。每当 ref 被附加到一个另一个节点,React 就会调用 callback
在这个案例中,我们没有选择使用 useRef
,因为当 ref 是一个对象时它并不会把当前 ref 的值的 变化 通知到我们。使用 callback ref 可以确保 即便子组件延迟显示被测量的节点 (比如为了响应一次点击),我们依然能够在父组件接收到相关的信息,以便更新测量结果。
注意到我们传递了 []
作为 useCallback
的依赖列表。这确保了 ref callback 不会在再次渲染时改变,因此 React 不会在非必要的时候调用它。
在此示例中,当且仅当组件挂载和卸载时,callback ref 才会被调用,因为渲染的 <h1>
组件在整个重新渲染期间始终存在。如果你希望在每次组件调整大小时都收到通知,则可能需要使用 ResizeObserver
或基于其构建的第三方 Hook。
如果你愿意,你可以 把这个逻辑抽取出来作为 一个可复用的 Hook:
function MeasureExample() {
const [rect, ref] = useClientRect(); return (
<>
<h1 ref={ref}>Hello, world</h1>
{rect !== null &&
<h2>The above header is {Math.round(rect.height)}px tall</h2>
}
</>
);
}
function useClientRect() {
const [rect, setRect] = useState(null);
const ref = useCallback(node => {
if (node !== null) {
setRect(node.getBoundingClientRect());
}
}, []);
return [rect, ref];
}