reactjs - React — 这是对 useCallback 钩子的好用吗?
问题描述
我试图了解使用useCallback
钩子定义所有回调和/或事件处理程序的新趋势背后的基本原理。
据我了解onClick === Callback_A
,只要x
保持不变,它就是正确的:
const onClick = useCallback(() => 42, [x]);
// ^^^^^^^^
// Callback_A
然而,函数表达式(的第一个参数useCallback
)在每次渲染后总是一个新的。只有x
在没有改变的情况下才会被丢弃。
假设一个组件由于某种原因渲染了 3 次,但x
始终保持不变:
/*********************************** 1st render ***/
const onClick = useCallback(() => 42, [x]);
// ^^^^^^^ ^^^^^^^^
// Callback_A Callback_A
/*********************************** 2nd render ***/
const onClick = useCallback(() => 42, [x]);
// ^^^^^^^ ^^^^^^^^
// Callback_A Callback_A'
/*********************************** 3rd render ***/
const onClick = useCallback(() => 42, [x]);
// ^^^^^^^ ^^^^^^^^
// Callback_A Callback_A''
() => 42
如果在每次渲染时创建一个新的函数表达式(即),无论x
是否相同,为什么不直接将其分配给onClick
呢?
当我在代码审查期间进行观察时,答案始终是"it-is-to-avoid-useless-re-rendering-of-child-components" 的变体。
我想对这个理论进行测试,并创建了一个带有五个按钮的简单应用程序:(
见下面的截屏视频)
- 每个按钮都与它自己的单一状态相关联。
- 该应用程序每两秒重新渲染一次,仅更新一个按钮的值。
- 所有其他按钮始终保持不变
- 每个按钮都有自己的“onclick”处理程序。
- 每个“onclick”处理程序都以不同的方式“生成”(例如有
useCallback
或没有)
我计算生成了多少次 onclick 处理程序以及每个按钮实际使用了多少个 onclick 处理程序:
import { useCallback, useEffect, useState } from "react";
const stats =
{ N: {}
, B: {}
, C: {}
, D: {}
, E: {} };
const register_handler = btn => {
const now = performance.now();
stats[btn][now] = false;
return () => stats[btn][now] = true;
};
const print_stats = () => {
console.table(
Object.entries(stats).map(([button, handlers]) => ({
button,
count_generated: Object.keys(handlers).length,
count_executed: Object.values(handlers).filter(b => b).length
}))
);
};
const e_click = register_handler('E');
export default function App() {
// Only `n` will change. All other vars will remain the same.
const [n, write_n] = useState(1);
const [b] = useState('B');
const [c] = useState('C');
const [d] = useState('D');
const [e] = useState('E');
// Forces <App/> to re-render every two seconds.
useEffect(() => setTimeout(() => write_n(n + 1), 2000));
const n_click = useCallback(register_handler('N'), [n]);
const b_click = useCallback(register_handler('B'), [b]);
const c_click = register_handler('C');
return (
<fieldset>
<legend>your buttons are belong to us</legend>
<button value={n} onClick={n_click}>{n}</button>
<button value={b} onClick={b_click}>{b}</button>
<button value={c} onClick={c_click}>{c}</button>
<button value={d} onClick={register_handler('D')}>{d}</button>
<button value={e} onClick={e_click}>{e}</button>
<hr />
<button onClick={print_stats}>print stats</button>
</fieldset>
);
}
我准确地点击每个按钮三次,然后打印一些统计数据:
按钮 | 生成的处理程序数 | 使用的处理程序数量 |
---|---|---|
ñ | 24 | 3 |
乙 | 24 | 1 |
C | 24 | 3 |
D | 24 | 3 |
乙 | 1 | 1 |
观察:
- 除了按钮之外,
E
所有其他按钮都生成了太多的事件处理程序。 - 两个按钮都
C
没有D
使用useCallback
,但他们没有重新渲染。
除了按钮之外,E
所有其他按钮都生成了相同数量的事件处理程序,但只消耗了一些。有些人自始至终重复使用相同的事件处理程序,而另一些人每次都使用一个新的事件处理程序。我们对缓存的好处useCallback
超过它的成本有多大信心?我想任何涉及缓存的东西都会带来一些权衡。
这种无条件的爱useCallback
也伴随着一些不平凡的维护成本。不是每个人都熟悉它,我怀疑它的某些使用是由某种程度的货物崇拜编程驱动的。
在这个特定的例子中,任何人都有理由使用useCallback
,为什么?
https://codesandbox.io/s/experimenting-with-usecallback-c4jf1
解决方案
推荐阅读
- javascript - 如何使用 jest 模拟 javascript 类的除一种方法之外的所有方法?
- javascript - 使用js调用rest API并执行post
- python - 从firestore获取文档字段的字段类型
- java - 实体创建引发错误“无法解析符号‘保存’”
- java - 如何获取 DataMongoTest 注释以使用嵌入式 mongo 服务器
- macos - Docker 桌面中未显示 Kubernetes 菜单(Mac)
- node.js - AWS Javascript SDK v3 中的 S3 getSignedUrl v2 等效项
- python - 升级 py2neo 版本代码后出现错误
- python - How to check of exceptions in nested functions when using MagicMock objects in Pytest?
- java - 延迟加载 @OneToOne 与 Hibernate 的关系