dip*_*pea 9 reactjs react-hooks
这是 React 即将推出的钩子的准确填充吗useEffectEvent?它是否存在任何潜在问题?
我不确定的一部分是,在返回的函数用于任何其他效果之前是否保证已更新引用。我认为我在这方面没问题,只要我从不useInsertionEffect在代码中的其他任何地方使用相当深奥的东西,但我想确认这一点。
function useEffectEvent(callback) {
const fnRef = useRef(null)
useInsertionEffect(() => {
fnRef.current = callback
})
return (...args) => {
return fnRef.current.apply(null, args)
}
}
Run Code Online (Sandbox Code Playgroud)
我不认为 React 团队已经提出了官方的 polyfill useEffectEvent(至少我还没有看到它)。
话虽如此,您可以在RFC 中看到一个polyfill,用于useEvent最初使用的现已不复存在的useLayoutEffect. 在将事件与效果分离的早期版本中,Dan Abramov 展示了一个如下所示的更新版本(在文档中不再可见):
import { useRef, useInsertionEffect, useCallback } from 'react';
// The useEvent API has not yet been added to React,
// so this is a temporary shim to make this sandbox work.
// You're not expected to write code like this yourself.
export function useEvent(fn) {
const ref = useRef(null);
useInsertionEffect(() => {
ref.current = fn;
}, [fn]);
return useCallback((...args) => {
const f = ref.current;
return f(...args);
}, []);
}
Run Code Online (Sandbox Code Playgroud)
此填充使用useInsertionEffect. 我相信这种选择的原因是插入效果是最先运行的(与useLayoutEffect和相比useEffect)。因此,可以非常安全地假设在任何其他效果运行之前更新了引用。
后来,RFCuseEvent 被搁置并useEffectEvent开始在文档中显示。useEffectEvent尽管如此,由于公开的行为是相同的,因此可以重复使用相同的 polyfill 来实现。
请注意,使用这个polyfill,钩子会返回一个稳定的函数(感谢useCallback(..., [])),这可能不是必需的,但更安全(以防消费者错误地将返回的函数添加到效果的依赖项中)。
如果您想了解有关 polyfill 的更多信息,我已经在博客中介绍过它:A Look inside the useEvent polyfill from the new React docs。
阅读当前文档,您可以将您的方法与使用 useEffectEvent 原理的另一个 polyfill进行比较:它与您提供的原始 polyfill 非常相似,唯一的区别是回调函数立即分配给ref:
const useEffectEvent = (callback) => {
const ref = useRef(callback);
ref.current = callback;
return (...args) => {
ref.current(...args);
}
}
Run Code Online (Sandbox Code Playgroud)
该polyfilluseEffectEvent尝试通过存储对回调函数的引用并返回调用最新版本回调的函数来模仿功能。
关于您的问题是否ref保证在返回的函数用于任何其他效果之前已更新,应该ref在其他效果中使用时更新 ,因为引用是在效果运行之前的渲染阶段更新的。
在这两个 polyfill 中,ref都会在调用返回的函数之前更新,以确保使用最新版本的回调。
但是,在这两种情况下都需要考虑一些注意事项:
这两种解决方案都不提供 的确切语义useEffectEvent。它们不会自动“打破效果的反应性和不应反应的代码之间的链条”。
它们不会阻止回调从调用钩子的渲染中捕获值。这意味着,如果回调使用任何道具或状态,并且这些道具或状态在调用钩子和调用回调之间发生变化,则可能会使用过时的值。
useInsertionEffect不建议在您的 polyfill 中使用,除非您正在开发 CSS-in-JS 库并且需要一个地方来注入样式。根据React 文档,useInsertionEffect在任何 DOM 突变之前触发,主要设计用于在任何 DOM 更新发生之前注入样式。
因此,这些 Polyfill 并不完美,也没有完全模仿即将到来的useEffectEvent钩子的行为。
详细说明:
再次,从“将事件与效果分离”中,我相信useEffectEvent其设计目的是将非反应性逻辑与固有的反应性逻辑分开。useEffect. 这意味着效果事件内部的逻辑是非反应性的,并且始终“看到”道具和状态的最新值,无论何时调用它。这是这些 Polyfill 试图模仿的基本行为。
但这些腻子可能与官方有所不同,useEffectEvent因为:
回调执行的时机:在 React 中,效果执行的时机是经过精心管理的。效果在渲染阶段之后运行,确保它们始终具有最新的状态和道具。然而,polyfills 使用 aref来存储回调,然后返回一个调用最新版本回调的函数。这可能会带来计时问题,即在使用ref最新回调更新之前调用回调。
反应性和依赖项跟踪:在 polyfill 中,钩子的用户有责任确保回调中使用的任何 props 或状态都得到正确管理。官方useEffectEvent设计为自动处理这个问题。另请参阅facebook/reactPR 25881
这意味着,如果回调使用任何道具或状态,并且这些道具或状态在调用钩子和调用回调之间发生变化,则可能会使用过时的值。
与React的内部机制集成:官方useEffectEvent将与React的内部机制集成,用于管理效果和更新。它旨在与 React 的其余功能无缝协作,提供一致且可预测的行为。另一方面,polyfills 是在用户空间中实现的,可能不会以相同的方式与 React 的内部机制交互。这可能会导致官方挂钩中不存在的意外行为或边缘情况。
与未来 React 功能的兼容性:随着 React 的不断发展,新功能和变化可能会影响效果和回调的工作方式。官方useEffectEvent将与 React 的其余部分一起维护和更新,以确保与未来更改的兼容性。然而,polyfill 可能与 React 的未来更改不兼容,并且可能需要更新或修改才能继续按预期工作。
这些 polyfill 对于访问从效果调用的回调中的最新值非常有用,但是它们可能无法完全复制useEffectEvent.
关于第2点:
“这意味着,如果回调使用任何道具或状态,如果这些道具或状态在调用钩子和调用回调之间发生变化,则可能会使用过时的值。”
在 React 中,函数组件根据 props 和 state 的变化进行渲染。在此渲染期间,将调用组件函数,并使用 props 和 state 的当前值执行函数内的所有代码。
当在组件内部创建回调函数时,它会捕获闭包中 props 和 state 的当前值。这称为“关闭”道具或状态。如果稍后调用回调,它将使用创建时的 props 和 state 的值,而不是当前值。
这对于事件处理程序(例如onClick)通常很好,因为它们运行是为了响应用户操作,而不是响应 React 重新渲染。但是,对于传递给回调useEffect或类似的挂钩,这可能会导致问题。这些回调通常旨在响应 props 或状态的变化,因此如果它们使用过时的值,它们可能无法正确运行。
例如:
function MyComponent({ prop }) {
useEffect(() => {
console.log(prop);
}, []);
}
Run Code Online (Sandbox Code Playgroud)
在此组件中,useEffect回调将记录 的初始值prop,但不会记录 的任何更改prop。这是因为回调在 的初始值上关闭prop,并且不会“看到”后续更改。
要解决此问题,您可以添加prop到以下依赖项数组中useEffect:
function MyComponent({ prop }) {
useEffect(() => {
console.log(prop);
}, [prop]);
}
Run Code Online (Sandbox Code Playgroud)
现在,只要发生变化,回调就会被重新创建(从而关闭 的新值prop)prop。
使用useEffectEvent,回调应该总是“看到” props 和 state 的最新值,而不需要重新创建。这与 不同useEffect,这是使用的主要原因之一useEffectEvent。
在您发布的 Polyfill 中,钩子的用户(即编写组件的开发人员)必须手动确保回调使用正确的 props 和 state 值。这可能涉及使用useRef来存储最新值,或更频繁地重新创建回调,或其他一些策略。
在最坏的情况下,如果开发人员没有正确管理 props 或 state,回调可能会使用过时的值,从而导致不正确的行为。useEffectEvent这是使用polyfills而不是真实的polyfills (当它可用时)时存在的风险。
我相信直接在渲染内(而不是在效果内)更新的填充版本
ref在并发反应中是不安全的(因为这意味着渲染不再是“纯粹的”)。
这就是我useInsertionEffect在我的版本中使用的原因。
正确:在并发模式下,React 可能会选择开始渲染给定组件,然后暂停此渲染过程以开始处理其他更紧急的事情,最后返回完成它开始的事情。
正如其中一个 polyfill 所做的那样,直接在渲染阶段更新refa 可能会导致并发模式场景中的不一致,因为如果 React 开始渲染组件,然后暂停并重新启动渲染(由于更高优先级的更新),则ref将更新两次 - 一次用于初始中断的渲染,另一次用于重新启动的渲染。这可能会导致不可预测的行为,因为ref值可能与效果运行时的状态和道具不对应。
另一方面,您的 polyfill 版本用于useInsertionEffect更新ref. 该useInsertionEffect钩子旨在在任何 DOM 突变发生之前、渲染阶段完成之后执行。这确保了效果运行时,渲染阶段不会被中断或重新启动,并且ref具有正确的值。
然而,值得注意的是,这useInsertionEffect主要是为了让 CSS-in-JS 库在任何 DOM 突变发生之前插入样式。可能不建议将其用于其他目的,并且可能有其自己的边缘情况或问题。例如,到时候useInsertionEffect运行时,引用尚未附加,并且 DOM 尚未更新。
所以,是的,useInsertionEffect可以成为避免在并发模式的渲染阶段直接更新引用的解决方案。
但它并非没有警告,而且这也不是该钩子的预期用例。
| 归档时间: |
|
| 查看次数: |
2309 次 |
| 最近记录: |