这是对 useCallback 和 useMemo 的错误使用吗?
Is this incorrect use of useCallback and useMemo?
我们什么时候应该担心功能组件重新定义变量和函数?
我认为第一种情况显然是不必要的,创建函数并不昂贵(并且 React 开发工具配置文件测试没有检测到 useCallback 的性能变化)
import React, { useState, useMemo, useCallback } from "react";
export const App = () => {
const [counter, setCounter] = useState(1);
const showCounter = useCallback(() => {
console.log(counter);
}, [counter]);
return (
<>
<button onClick={showCounter}>Show</button>
<button onClick={() => setCounter(counter + 1)}>Add</button>
</>
);
};
另一方面,在另一个示例中,使用 React 开发工具,我们可以检测到 useMemo 将渲染时间缩短了一半:
import React, { useState, useMemo, useCallback } from "react";
export const App = () => {
const [counter, setCounter] = useState(1);
const [list, setList] = useState<number[]>([]);
function setListItem(item: number) {
setList([...list, item]);
}
//should we useMemo here?
const domList = useMemo(
() =>
list.map((value: number, index: number) => (
<li key={index}> {value} </li>
)),
[list]
);
return (
<>
<p>{counter}</p>
<button onClick={() => setCounter(counter - 1)}>Subtract</button>
<button onClick={() => setCounter(counter + 1)}>Add</button>
<button onClick={() => setListItem(counter)}>Push to array</button>
<h1>List</h1>
<ul>{domList}</ul>
</>
);
};
但是当我们谈论前端时,仍然不到一毫秒的增益和数组映射和 JSX 数组并不是真正昂贵的东西。然而,在每次渲染中留下一个数组映射方法 运行 是很不舒服的。那么,我们应该做什么呢? (让我们忘记什么时候我们有 useEffect 取决于一些使 useMemo 成为必要的变量)
- 在不需要在每次渲染中重新定义的所有函数和变量中使用 useCallback 和 useMemo。
- useCallback 和 useMemo 仅用于我们可以清楚地看到性能提升的事情(即使大多数时间不会算作 UX 的改进)。所以我们在第二个例子中需要 useMemo。
- useCallback 和 useMemo 仅当在特定情况下不使用时,我们的用户体验才会大幅下降。所以只有非常昂贵的函数才会使用回调
您获得的性能各不相同。是的,对于 useMemo
,如果您正在记忆的计算很昂贵,它会提供明显的直接好处。但是,对于 useCallback
和 useMemo
,长期收益是指 avoiding unnecessary renders in child components,而不是重新计算成本。
请记住,如果组件中的任何道具发生变化,它们可能会触发渲染调用,并且这种情况在组件树中越高,对性能的影响就越大。这就是我们通常使用这些钩子的原因。
一般情况下,组件都有几个状态元素,通过memo hooks,我们可以建立依赖关系,避免计算冗余。例如,在您的综合案例中,您有两个状态元素:counter
和 list
。每次 counter
更改时,您都会通过使其仅依赖于 list
来防止重新计算 domList
。如果这是一个叶节点,肯定没有性能提升,但是,这是组件树的根,如果没有这些钩子,性能将不会随着添加更多状态元素而扩展,您将不得不重构代码以解决性能问题。当然,如果列表中的元素超过 1000 个,<ul>{domList}</ul>
的成本可能会很高,但 <ul><ExpensiveCutie domList={domList} /></ul>
可能需要数周的时间来注意、调试和重构。
简而言之,我建议在容器组件中使用备忘录挂钩,而不要在叶组件中使用。然而,如果您的叶组件在您实现它时变成了容器怎么办?
在你走之前,你甚至需要使用 memo() 吗?阅读更多 here.
我们什么时候应该担心功能组件重新定义变量和函数?
我认为第一种情况显然是不必要的,创建函数并不昂贵(并且 React 开发工具配置文件测试没有检测到 useCallback 的性能变化)
import React, { useState, useMemo, useCallback } from "react";
export const App = () => {
const [counter, setCounter] = useState(1);
const showCounter = useCallback(() => {
console.log(counter);
}, [counter]);
return (
<>
<button onClick={showCounter}>Show</button>
<button onClick={() => setCounter(counter + 1)}>Add</button>
</>
);
};
另一方面,在另一个示例中,使用 React 开发工具,我们可以检测到 useMemo 将渲染时间缩短了一半:
import React, { useState, useMemo, useCallback } from "react";
export const App = () => {
const [counter, setCounter] = useState(1);
const [list, setList] = useState<number[]>([]);
function setListItem(item: number) {
setList([...list, item]);
}
//should we useMemo here?
const domList = useMemo(
() =>
list.map((value: number, index: number) => (
<li key={index}> {value} </li>
)),
[list]
);
return (
<>
<p>{counter}</p>
<button onClick={() => setCounter(counter - 1)}>Subtract</button>
<button onClick={() => setCounter(counter + 1)}>Add</button>
<button onClick={() => setListItem(counter)}>Push to array</button>
<h1>List</h1>
<ul>{domList}</ul>
</>
);
};
但是当我们谈论前端时,仍然不到一毫秒的增益和数组映射和 JSX 数组并不是真正昂贵的东西。然而,在每次渲染中留下一个数组映射方法 运行 是很不舒服的。那么,我们应该做什么呢? (让我们忘记什么时候我们有 useEffect 取决于一些使 useMemo 成为必要的变量)
- 在不需要在每次渲染中重新定义的所有函数和变量中使用 useCallback 和 useMemo。
- useCallback 和 useMemo 仅用于我们可以清楚地看到性能提升的事情(即使大多数时间不会算作 UX 的改进)。所以我们在第二个例子中需要 useMemo。
- useCallback 和 useMemo 仅当在特定情况下不使用时,我们的用户体验才会大幅下降。所以只有非常昂贵的函数才会使用回调
您获得的性能各不相同。是的,对于 useMemo
,如果您正在记忆的计算很昂贵,它会提供明显的直接好处。但是,对于 useCallback
和 useMemo
,长期收益是指 avoiding unnecessary renders in child components,而不是重新计算成本。
请记住,如果组件中的任何道具发生变化,它们可能会触发渲染调用,并且这种情况在组件树中越高,对性能的影响就越大。这就是我们通常使用这些钩子的原因。
一般情况下,组件都有几个状态元素,通过memo hooks,我们可以建立依赖关系,避免计算冗余。例如,在您的综合案例中,您有两个状态元素:counter
和 list
。每次 counter
更改时,您都会通过使其仅依赖于 list
来防止重新计算 domList
。如果这是一个叶节点,肯定没有性能提升,但是,这是组件树的根,如果没有这些钩子,性能将不会随着添加更多状态元素而扩展,您将不得不重构代码以解决性能问题。当然,如果列表中的元素超过 1000 个,<ul>{domList}</ul>
的成本可能会很高,但 <ul><ExpensiveCutie domList={domList} /></ul>
可能需要数周的时间来注意、调试和重构。
简而言之,我建议在容器组件中使用备忘录挂钩,而不要在叶组件中使用。然而,如果您的叶组件在您实现它时变成了容器怎么办?
在你走之前,你甚至需要使用 memo() 吗?阅读更多 here.