React 不会让我以完全合理的方式使用 `useEffect`

Posted

技术标签:

【中文标题】React 不会让我以完全合理的方式使用 `useEffect`【英文标题】:React won't let me use `useEffect` in a completely reasonable way 【发布时间】:2021-11-12 15:28:00 【问题描述】:

我创建了以下辅助函数,因为 React 中的功能组件没有挂载和卸载事件。我不在乎人们怎么说; useEffect 不是等价物。可以如下所示:

//eslint-disable-next-line
export const useMount = callback => useEffect(callback, []);

//eslint-disable-next-line
export const useUnmount = callback => useEffect(() => callback, []);

React 不允许我这样做,因为我在技术上是从非组件函数调用 useEffect。我这样做是因为当我使用 useEffect 作为挂载或卸载事件时,它会污染我的终端,并发出关于不包括依赖列表中的某些内容的无意义警告。我知道,我应该这样做……

export default function MusicPlayback(...) 
    ...
    useEffect(() => stopMusic, []);
    ...

但随后我收到关于 stopMusic 未作为依赖项包含在内的警告。但我不希望它成为依赖项,因为这样useEffect 将不再是卸载事件,并且每次渲染都会调用stopMusic

我知道是 eslint 警告我,我可以使用 //eslint-disable-next-line 但这太难看,无法在需要卸载处理程序的每个文件中使用。

据我所知,如果不使用//eslint-disable-next-line,就无法在任何地方都使用卸载处理程序。有没有办法解决这个问题?

【问题讨论】:

【参考方案1】:

好的,依赖性检查是有原因的,即使你认为它不应该在那里。

  useEffect(() => 
    stopMusic()
    ...
  , [stopMusic, ...])

我们来谈谈stopMusic,假设这是另一个第三方的全局函数。如果实例永远不会改变,那么您应该将其作为依赖项触发,因为它不会受到伤害。

如果stopMusic 实例确实发生了变化,那么您需要问自己为什么不想将其作为依赖项,因为它可能会意外调用旧的stopMusic

现在,假设您对所有这些都很好,但仍然不希望它与stopMusic 连接,那么请考虑使用参考。

  const ref = useRef( stopMusic )

  useEffect(() => ref.current.stopMusic(), [ref])

无论哪种方式,您都明白这一点,它必须取决于某些东西,也许您的业务逻辑不希望这样做。但从技术上讲,只要您需要调用不属于useEffect 的内容,它就必须是一个依赖项。否则从useEffect 的角度来看,这是一个不同步的问题。 ref(或任何对象)的重点是故意进入这种不同步状态。

当然,如果你真的讨厌这个 linter 规则,我相信你可以禁用它。

注意

React 社区正在提议一种在未来为您添加这些依赖项的方法。其背后的原因是 React 旨在对单向列车中的数据做出反应。

【讨论】:

问题是useEffect所依赖的东西不是stopMusicstopMusic调用的是实体,函数和实体有两个完全不同的生命周期。阅读下面我的答案了解更多信息。【参考方案2】:

这就是我最终不得不在卸载时停止音乐的操作。

export default function MusicPlayback(...) 
    const [playMusic, stopMusic] = useMagicalSoundHookThingy(myMusic);
    const stopMusicRef = useRef(stopMusic);
    stopMusicRef.current = stopMusic; // Gotta do this because stopMusic no longer works after render.
    ...
    useEffect(() => 
        const stopMusicInHere = stopMusicRef.current; // Doing this to avoid a warning telling me that when called the ref will probably no longer be around.
        return stopMusicInHere;
    , [stopMusicRef]);
    ...

使用这样的 ref 没有意义。愚弄 eslint 只是一个聪明的技巧。我们正在将每次渲染都会发生变化的东西打包成不会发生变化的东西。这就是我们所做的一切。

我遇到的问题是我与之交互的实体是静态的。但是与该实体(即函数stopMusic)进行通信的方式是暂时的。因此,useEffect 确定依赖关系的蛮力手段不够细致,无法真正表明某些依赖关系是否实际发生了变化。只是调用该依赖项的 tiddlywinks、钩子创建的函数和对象。也许这是钩子作者的错。也许 tiddlywinks 应该保持与实体相同的生命周期。

我非常喜欢 React,但这是我有一段时间的烦恼,我厌倦了人们告诉我应该只包含所有依赖项 eslint 要求,就好像我真的不明白什么实际上涉及依赖项。在 React 程序中完全没有任何副作用可能是理想的,并且依赖像 Redux 这样的数据存储管道来提供任何上下文。但这是现实世界,总会有生命周期不连贯的实体。背景中播放的音乐就是这样一种实体。这就是生活。

【讨论】:

我选择了@windmaomao 的答案作为正确答案,因为无论我喜欢与否,他所说的一切都是正确的。 感谢您接受答案,我有点同意您的看法。当我开始时,我倾向于禁用这条 linter 线。您可以找到一个全局设置来完全禁用它。但是,如果您即将做错事,您也可以将此作为一些帮助来警告您。如果您非常确定自己在做什么,请忽略此警告或禁用它。我认为在这种情况下不使用依赖项和useRef 应该很好。 是的,这个 lint 错误通常非常有助于提醒我包含我忘记的依赖项。所以我实际上大部分时间都很欣赏它。这些边缘情况只会让人讨厌。我从不全局禁用警告或 linter。我发现这样做会产生无法察觉的问题。短绒和警告的存在是有原因的,即使它不是一个完全好的原因。只是他们有时可能是错的。

以上是关于React 不会让我以完全合理的方式使用 `useEffect`的主要内容,如果未能解决你的问题,请参考以下文章

如果侧面菜单是浮动的还是固定的,有没有办法让我以编程方式获取?

React Native 中如何使用手机摄像头拍照并获取数据

使用 ajax 将 textarea 信息存储到数据库中。除非我以任何方式编辑 textarea,否则工作正常。信息仍然被存储,但 XMLHTTP 永远不会关闭

React - 当 Axios 在单独的组件中调用时,setState 不会重新渲染页面

[Fancybox 2.1加载AJAX模板,顶部留有很大的空白,但是当我以其他方式使用Fancybox时,不会发生此问题

在 React 和 Material-UI 中,如何让我的 Grid 项目占用 100% 的可用水平空间,而不会换行到下一行?