React useEffect()仅在具有依赖项的第一个渲染上运行

时间:2019-09-23 00:36:52

标签: reactjs typescript react-hooks

关于此还有其他一些SO问题,答案要么是通过ESLint消除依赖项投诉(我正在使用打字稿),要么要做其他事情以仍然将useEffect的第二个参数设为[] 。但是,根据React relocation R_X86_64_32 against `.data' can not be used when making a shared object; recompile with -fPIC" libsshl.a,不建议这样做。同样在react useEffect文档下

  

如果传递一个空数组([]),则效果中的道具和状态   将始终具有其初始值。同时通过[]作为第二个   参数更接近熟悉的componentDidMount和   componentWillUnmount心智模型,通常有更好的解决方案   避免过于频繁地重新运行效果。另外,别忘了React   将useEffect推迟到浏览器绘制完毕后再进行,这样   多余的工作没什么问题。

我有以下代码:

  useEffect(() => {
    container.current = new VisTimeline(container.current, items, groups, options);
  }, [groups, items, options]);

我希望它只运行一次。

绕过它的唯一方法是让它每次都运行,并且useState跟踪它是否已经像这样运行过:

  const [didLoad, setDidLoad] = useState<boolean>(false);

  useEffect(() => {
    if (!didLoad) {
      container.current = new VisTimeline(container.current, items, groups, options);
      setDidLoad(true);
    }
  }, [didLoad, groups, items, options]);

4 个答案:

答案 0 :(得分:4)

我现在处理此问题的方法是将适当的依赖项放入依赖项列表中。

因为我只希望该效果只运行一次,并且由于该效果仅在组件首次安装时依赖于某些数据,所以可以忽略那些依赖关系。例如,groups道具可能会在以后更改,但此效果无需再次运行。

但是,作为一种习惯,我不会忽略建议的依赖项,而是始终列出它们。如果我有意遗漏某些东西,我会添加一个eslint ignore语句(虽然还没有遇到需要这样做的事情)...只要您了解数据发生变化时正在发生的事情,就可以遵循任何约定。效果不起作用。

但是,如果您要列出依赖项,我提出的代码也不是最佳解决方案:

 const [didLoad, setDidLoad] = useState<boolean>(false);

  useEffect(() => {
    if (!didLoad) {
      container.current = new VisTimeline(container.current, items, groups, options);
      setDidLoad(true);
    }
  }, [didLoad, groups, items, options]);

此效果运行时,我不想引起渲染。因此,我将使用ref(不需要是依赖项)来代替使​​用状态。

 const timelineLoaded = useRef<boolean>(false);

  useEffect(() => {
    if (!timelineLoaded.current) {
      container.current = new VisTimeline(container.current, items, groups, options);
      timelineLoaded.current = true;
    }
  }, [groups, items, options]);

答案 1 :(得分:0)

useEffect(() => {
    container.current = new VisTimeline(container.current, items, groups, options);
  }, [groups, items, options]);

上面的代码每次在数组中的变量之一更改时就运行该函数。如果您只想运行一次,那么[]应该是文档中提到的数组。所以基本上

useEffect(() => {
    container.current = new VisTimeline(container.current, items, groups, options);
  }, []);

希望这会有所帮助。

答案 2 :(得分:0)

添加额外的代码来解决工具问题并不好。

解决实际问题 - 在这种情况下,从 linter 中排除您知道以您想要的方式工作的代码。

在您知道不需要 useEffect 依赖项数组中的值的代码上特别禁用 linting。在 useEffect 代码块上方添加:

/* eslint-disable react-hooks/exhaustive-deps */

答案 3 :(得分:-1)

这个自定义钩子是 useEffect 上的一个包装器,只会在第一次渲染后运行并且不违反 react-hooks/exhaustive-deps eslint 规则:

import { useEffect } from 'react';

const useEffectOnlyOnce = func => useEffect(func, []);

export default useEffectOnlyOnce;

可以这样使用:

import useEffectOnlyOnce from '<path-to-your-custom-hook>/useEffectOnlyOnce'

...

  useEffectOnlyOnce(() => {
    container.current = new VisTimeline(container.current, items, groups, options);
  });

...

此解决方案来自 CSS Tricks 上的 comment 帖子