useEvent 要解决一个问题:如何同时保持函数引用不变与访问到最新状态。
本周我们结合 RFC 原文与解读文章 What the useEvent React hook is (and isn't) 一起了解下这个提案。
借用提案里的代码,一下就能说清楚 useEvent
是个什么东西:
function Chat() {
const [text, setText] = useState('');
// ✅ Always the same function (even if `text` changes)
const onClick = useEvent(() => {
sendMessage(text);
});
return <SendButton onClick={onClick} />;
}
onClick
既保持引用不变,又能在每次触发时访问到最新的 text
值。
为什么要提供这个函数,它解决了什么问题,在概述里慢慢道来。
定义一个访问到最新 state 的函数不是什么难事:
function App() {
const [count, setCount] = useState(0)
const sayCount = () => {
console.log(count)
}
return <Child onClick={sayCount} />
}
但 sayCount
函数引用每次都会变化,这会直接破坏 Child
组件 memo 效果,甚至会引发其更严重的连锁反应(Child
组件将 onClick
回调用在 useEffect
里时)。
想要保证 sayCount
引用不变,我们就需要用 useCallback
包裹:
function App() {
const [count, setCount] = useState(0)
const sayCount = useCallback(() => {
console.log(count)
}, [count])
return <Child onClick={sayCount} />
}
但即便如此,我们仅能保证在 count
不变时,sayCount
引用不变。如果想保持 sayCount
引用稳定,就要把依赖 [count]
移除,这会导致访问到的 count
总是初始值,逻辑上引发了更大问题。
一种无奈的办法是,维护一个 countRef,使其值与 count 保持同步,在 sayCount
中访问 countRef
:
function App() {
const [count, setCount] = useState(0)
const countRef = React.useRef()
countRef.current = count
const sayCount = useCallback(() => {
console.log(countRef.current)
}, [])
return <Child onClick={sayCount} />
}
这种代码能解决问题,但绝对不推荐,原因有二:
useEffect
里又太麻烦。另一种办法就是自创 hook,如 useStableCallback
,这本质上就是这次提案的主角 - useEvent
:
function App() {
const [count, setCount] = useState(0)
const sayCount = useEvent(() => {
console.log(count)
})
return <Child onClick={sayCount} />
}
所以 useEvent
的内部实现很可能类似于自定义 hook useStableCallback
。在提案内也给出了可能的实现思路:
// (!) Approximate behavior
function useEvent(handler) {
const handlerRef = useRef(null);
// In a real implementation, this would run before layout effects
useLayoutEffect(() => {
handlerRef.current = handler;
});
return useCallback((...args) => {
// In a real implementation, this would throw if called during render
const fn = handlerRef.current;
return fn(...args);
}, []);
}
其实很好理解,我们将需求一分为二看:
useCallback
并将依赖数组置为 []
。注意两段注释,第一个是 useLayoutEffect
部分实际上要比 layoutEffect
执行时机更提前,这是为了保证函数在一个事件循环中被直接消费时,可能访问到旧的 Ref 值;第二个是在渲染时被调用时要抛出异常,这是为了避免 useEvent
函数被渲染时使用,因为这样就无法数据驱动了。
其实 useEvent
概念和实现都很简单,下面我们聊聊提案里一些有意思的细节吧。
提案里提到,如果不考虑名称长短,完全用功能来命名的话,useStableCallback
或 useCommittedCallback
会更加合适,都表示拿到一个稳定的回调函数。但 useEvent
是从使用者角度来命名的,即其生成的函数一般都被用于组件的回调函数,而这些回调函数一般都有 “事件特性”,比如 onClick
、onScroll
,所以当开发者看到 useEvent
时,可以下意识提醒自己在写一个事件回调,还算比较直观。(当然我觉得主要原因还是为了缩短名称,好记)
虽然 useEvent
可以拿到最新值,但和 useCallback
拿 ref
还是有区别的,这个差异体现在:
function App() {
const [count, setCount] = useState(0)
const sayCount = useEvent(async () => {
console.log(count)
await wait(1000)
console.log(count)
})
return <Child onClick={sayCount} />
}
await
前后输出值一定是一样的,在实现上,count
值仅是调用时的快照,所以函数内异步等待时,即便外部又把 count
改了,当前这次函数调用还是拿不到最新的 count
,而 ref
方法是可以的。在理解上,为了避免夜长梦多,回调函数尽量不要写成异步的。
如果你坚持写出 onSomething={cond ? handler1 : handler2}
这样的代码,那么 cond
变化后,传下去的函数引用也一定会变化,这是 useEvent
无论如何也避免不了的,也许解救方案是 Lint and throw error。
其实将 cond ? handler1 : handler2
作为一个整体包裹在 useEvent
就能解决引用变化的问题,但除了 Lint,没有人能防止你绕过它。
不能。虽然提案里给了一个近似解决方案,但实际上存在两个问题:
useLayoutEffect
时机依然不够提前,如果值变化后理解访问函数,拿到的会是旧值。useEvent
显然又给 React 增加了一个官方概念,在结结实实增加了理解成本的同时,也补齐了 React Hooks 在实践中缺失的重要一环,无论你喜不喜欢,问题就在那,解法也给了,挺好。
本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/N9L7d43StIAGH89-WPASQA
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。