【发布时间】:2022-12-18 04:18:43
【问题描述】:
useCallback、useMemo 和useEffect 之间的主要区别是什么?
举例说明何时使用它们中的每一个。
【问题讨论】:
-
你读过hooks api doc吗?
-
@Vencovsky 我的朋友,文档无法回答所有问题。假设如果道具改变以改变状态,useMemo 和useEffect 会更好吗?
标签: reactjs react-hooks
useCallback、useMemo 和useEffect 之间的主要区别是什么?
举例说明何时使用它们中的每一个。
【问题讨论】:
标签: reactjs react-hooks
一个简短的解释。
它是类组件生命周期方法 componentDidMount、componentWillUnmount、componentDidUpdate 等的替代方法。您还可以使用它在依赖项更改时创建副作用,即“如果某些变量更改,则执行此操作”。
在每次渲染时,功能组件内的所有内容都将再次运行。如果子组件依赖于父组件的函数,则每次父组件重新渲染时,子组件都会重新渲染,即使该函数“没有改变”(引用发生变化,但函数的作用不会改变)吨)。
它用于通过避免子项进行不必要的渲染来进行优化,使函数仅在依赖项发生变化时更改引用。
当函数是副作用的依赖项时,您应该使用它,例如useEffect。
它将在每个渲染器上运行,但具有缓存值。它只会在某些依赖项发生变化时使用新值。当你有昂贵的计算时,它用于优化。 Here is also a good answer that explains it。
【讨论】:
useEffect() 将允许您根据发送给它的依赖项在您的组件上创建副作用。
function Example() {
const [count, setCount] = React.useState(0);
React.useEffect(() => {
document.title = `You clicked ${count} times`;
}, [count]);
return (
<div>
<p>You clicked {count} times</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}
ReactDOM.render(<Example />, document.getElementById('root'))
<script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script>
<div id="root"></div>
上面的例子是taken from the documentation of React。您可以看到,每次单击按钮时,它都会触发计数字段的更新(使用 setCount()),然后,依赖于计数变量的效果将触发页面标题的更新。
useCallback() 将返回一个memoized 回调。通常,如果你有一个接收函数 prop 的子组件,在父组件每次重新渲染时,这个函数将被重新执行;通过使用useCallback(),您可以确保该函数仅在其依赖数组的任何值发生变化时才重新执行。
function ExampleChild({ callbackFunction }) {
const [value, setValue] = React.useState(0);
React.useEffect(() => {
setValue(value + 1)
}, [callbackFunction]);
return (<p>Child: {value}</p>);
}
function ExampleParent() {
const [count, setCount] = React.useState(0);
const [another, setAnother] = React.useState(0);
const countCallback = React.useCallback(() => {
return count;
}, [count]);
return (
<div>
<ExampleChild callbackFunction={countCallback} />
<button onClick={() => setCount(count + 1)}>
Change callback
</button>
<button onClick={() => setAnother(another + 1)}>
Do not change callback
</button>
</div>
)
}
ReactDOM.render(<ExampleParent />, document.getElementById('root'));
<script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script>
<div id="root"></div>
useMemo() 将返回一个 memoized 值,该值是传递参数的结果。这意味着 useMemo() 会对某个参数进行一次计算,然后它会从缓存中为相同的参数返回相同的结果。
当您需要处理大量数据时,这非常有用。
function ExampleChild({ value }) {
const [childValue, setChildValue] = React.useState(0);
React.useEffect(() => {
setChildValue(childValue + 1);
}, [value])
return <p>Child value: {childValue}</p>;
}
function ExampleParent() {
const [value, setValue] = React.useState(0);
const heavyProcessing = () => {
// Do some heavy processing with the parameter
console.log(`Cached memo: ${value}`);
return value;
};
const memoizedResult = React.useMemo(heavyProcessing, [value]);
return (
<div>
<ExampleChild value={memoizedResult} />
<button onClick={() => setValue(value + 1)}>
Change memo
</button>
</div>
)
}
ReactDOM.render(<ExampleParent />, document.getElementById('root'));
<script src="https://unpkg.com/react@16.8.0/umd/react.development.js"></script>
<script src="https://unpkg.com/react-dom@16.8.0/umd/react-dom.development.js"></script>
<div id="root"></div>
【讨论】:
最简单的解释:
使用效果:
每当您有一些逻辑作为对状态更改的反应或在更改即将发生之前执行时。
useEffect(() => {
// execute when state changed
() => {
// execute before state is changed
}
}, [state]);
或者在没有依赖的情况下:
useEffect(() => {
// execute when component has mounted
() => {
// execute when component will unmount
}
}, []);
使用回调:
每当您有一个取决于某些状态的功能时。此挂钩用于性能优化,并防止重新分配组件内的函数,除非依赖状态发生更改。
const myFunction = useCallback(() => {
// execute your logic for myFunction
}, [state]);
如果没有 useCallback,myFunction 将在每次渲染时重新分配。因此,与 useCallback 相比,它会使用更多的计算时间。
使用备忘录
每当您有一个取决于特定状态的值时。和 useCallback 一样,useMemo 也是为了性能优化减少重赋值。
const myValue = useMemo(() => {
// return calculated value
}, [state]);
与 useCallback 相同,myValue 仅在状态发生变化时分配,因此会减少计算时间。否则 myValue 将在每次渲染时重新分配。
!Trick 模仿 componentWillMount 生命周期
useMemo(() => {
// execute componentWillMount logic
]}, []);
因为 useEffect 在第一次渲染之后调用,然后在每次依赖项更改时调用。它永远不会在第一次渲染之前运行。 useMemo 与您的 JS 内联执行,因此将在它到达您的组件返回语句之前执行。
!笔记: 具有 useCallback 的函数和具有 useMemo 的值可以用作 useCallback、useMemo 和 useEffect 中的依赖项。强烈建议使用这些挂钩,以便在您的组件中拥有结构良好且可读的状态流。这些挂钩不会触发渲染。只有 useState 和 useReducer 可以!
如果你想保持不触发重新渲染或任何上述解释的钩子的状态,你可以使用 useRef。使用参考将在渲染时保持值一致,而不会触发任何状态相关值或效果。
【讨论】:
知道何时使用这些功能很好,但我想知道它们之间的实际区别是什么!这是我发现的:
useMemo 立即运行代码,因此返回值可用于其后的代码。这意味着它在第一次渲染之前运行,因此您用于访问 HTML 组件的任何 useRef 在初始运行时都将不可用。 (但是您可以将 ref.current 添加到 useMemo 依赖项,以便在 useRef 值可用时,在第一次渲染后再次运行 useMemo 代码)。由于返回值可用于直接跟随它的代码,这就是为什么建议不需要在每次渲染时重新运行的复杂计算的原因,因为立即可用的返回值可以避免您必须弄乱状态现在存储值并在以后访问它 - 只需获取 useMemo() 的返回值并立即使用它。useEffect 不会立即运行,而是在第一次渲染后运行。这意味着任何引用 HTML 元素的 useRef 值在第一次运行时都是有效的。由于它在您的函数中的所有代码都完成并呈现之后运行,因此没有返回值的意义,因为没有进一步运行的代码可以使用它。 useEffect 代码可以做任何可见的事情的唯一方法是改变状态以引起重新渲染,或者直接修改 DOM。useCallback 与 useMemo 相同,除了它记住函数本身而不是它的返回值。这意味着 useCallback 函数不会立即运行,但可以稍后运行(或根本不运行),而 useMemo 会立即运行其函数并保存其返回值以备后用。与 useMemo 不同,这不利于复杂的计算,因为代码每次使用时都会再次运行。如果您曾经将回调函数作为 prop 传递给渲染函数中的另一个组件,请确保传递 useCallback 的返回值。如果您将回调函数设置为const onClick = () => { ... },或者在 JSX 中将回调函数设置为onClick={() => { ... }},那么每次渲染您都会获得该函数的一个新实例,因此子组件将始终重新渲染,因为它认为您正在将回调更改为每个渲染器都有不同的功能。但是 useCallback 返回相同的每次都调用该函数的实例,因此如果没有其他更改,子函数可能会完全跳过渲染,从而使您的应用程序更具响应性。
例如,如果我们将相同的函数传递给useMemo 和useCallback:
let input = 123;
const output = useMemo(() => {
return input + 1;
}, [
input,
]);
// The above function has now run and its return value is available.
console.log( output ); // 124
input = 125; // no effect as the function has already run
console.log( output ); // 124
let input = 123;
const output = useCallback(() => {
return input + 1;
}, [
input,
]);
// The above function has not run yet but we can run it now.
console.log( output() ); // 124
input = 125; // changes the result as the function is running again
console.log( output() ); // 126
在这里,useCallback 记住了该函数,并将在未来的渲染中继续返回原始函数,直到依赖关系发生变化,而 useMemo 实际上立即运行该函数并只记住它的返回值。
useCallback() 和useMemo() 都提供了可以立即使用的返回值,而useEffect() 则没有,因为它的代码要等到渲染完成后很久才会运行。
【讨论】:
useMemo 中的依赖项,即 [input],useMemo 应该在依赖项更改时再次运行,因此对于各种输入值,结果都是正确的。
useEffect
当组件安装、卸载以及它的任何依赖项发生变化时被调用。
当组件为mounted、subscribe和unsubscribe时可用于获取数据到组件mounts和unmounts时的事件流(想想rxjs)。
const [userId, updateUser] = useState(1);
useEffect(()=>{
//subscription
const sub = getUser(userId).subscribe(user => user);
// cleanup
return () => {
sub.unsubscribe();
}
},[userId]) // <-- Will get called again when userId changes
也可用于不需要清理的一次性方法调用
useEffect(()=>{
oneTimeData();
},[]); // pass empty array to prevent being called multiple times
useCallback
是否有不想在每个组件渲染时都重新创建的函数?
想要一个不在组件挂载或卸载时调用的函数吗?
使用useCallback
const [val, updateValue] = useState(0);
const Compo = () => {
/* inc and dec will be re-created on every component render.
Not desirable a function does very intensive work.
*/
const inc = () => updateValue(x => x + 1);
const dec = () => updateValue(x => x - 1);
return render() {
<Comp1 onClick={inc} />
<Comp2 onClick={dec} />
}
}
useCallback 救援
const [val, updateValue] = useState(0);
const Compo = () => {
const callbackInc = useCallback(() => {
setCount(currentVal => currentVal + 1);
}, []);
const callbackDec = useCallback(() => {
setCount(currentVal => currentVal - 1);
}, []);
return render() {
<Comp1 onClick={callbackInc} />
<Comp2 onClick={callbackDec} />
}
}
如果传递给setCount 的参数不是函数,那么您希望useCallback“注意”的变量必须在 dependencies 数组中指定,否则不会有任何变化效果。
const callbackInc = useCallback(() => {
setCount(val + 1); // val is an 'outside' variable therefore must be specified as a dependency
}, [val]);
useMemo
做繁重的处理想memoize(缓存) 结果?使用useMemo
/*
heavyProcessFunc will only be called again when either val or val2 changes
*/
const result = useMemo(heavyProcessFunc(val, val2),[val,val2])
【讨论】:
所有这些钩子都有相同的目标:避免冗余组件重建(并重新执行钩子内的东西)。
useEffect 什么都不返回(无效),因此适用于这种情况。
useCallback 返回一个功能稍后将在组件中使用。与普通的函数声明不同,它不会触发组件重建,除非它的依赖项发生变化。
useMemo 只是useCallback 的另一种风格。
Here 是我迄今为止看到的最好的解释。
【讨论】: