简体   繁体   English

来自 React Hook 内部的变量 useEffect 将在每次渲染后丢失

[英]Variable from inside React Hook useEffect will be lost after each render

I have here a text animation that is working perfect.我在这里有一个完美的文本动画。 What I want to add now is an Intersection Observer so that the animation only starts once I scroll down to the Box.我现在要添加的是 Intersection Observer,这样动画只有在我向下滚动到 Box 时才会开始。

So what I did to achieve this is: I used the react hook useRef to use as reference to the element I want to observe and applied it to my Box with ref={containerRef}.所以我为实现这一点所做的是:我使用 react 钩子 useRef 用作对我想要观察的元素的引用,并将其应用到我的 Box 中,其中 ref={containerRef}。 Then declared a callback function that receives an array of IntersectionObserverEntries as a parameter, inside this function I take the first and only entry and check if it is intersecting with the viewport and if it is then it calls setIsVisible with the value of entry.isIntersecting (true/false).然后声明了一个回调函数,它接收一个 IntersectionObserverEntries 数组作为参数,在这个函数中,我采用第一个也是唯一一个条目并检查它是否与视口相交,如果是,那么它调用 setIsVisible 的值为 entry.isIntersecting (真假)。 After that I added the react hook useEffect and created an observer contructor using the callback function and the options I just created before.之后,我添加了反应钩子 useEffect 并使用回调函数和我之前创建的选项创建了一个观察者构造函数。 I implemented the logic in a new hook that I called useElementOnscreen.我在一个名为 useElementOnscreen 的新钩子中实现了逻辑。

It is working BUT I am getting a warning and cant solve it:它正在工作但我收到警告并且无法解决它:

1. Initial Code tsx file 1.初始代码tsx文件

const useElementOnScreen = <T,>(options: T): [MutableRefObject<HTMLDivElement | null>, boolean] => {
    const containerRef = useRef<HTMLDivElement | null>(null);
    const [isVisible, setIsVisible] = useState(false);

    const callbackFunction = (entries: IntersectionObserverEntry[]) => {
        const [entry] = entries;
        setIsVisible(entry.isIntersecting);
    };

    useEffect(() => {
        const observer = new IntersectionObserver(callbackFunction, options);

        if (containerRef.current) observer.observe(containerRef?.current);

        return () => {
            if (containerRef.current) observer.unobserve(containerRef?.current);
        };
    }, [containerRef, options]);

    return [containerRef, isVisible];
};

Here I get the warning: if (containerRef.current) observer.unobserve(containerRef?.current);在这里我收到警告: if (containerRef.current) observer.unobserve(containerRef?.current);

The warning is:警告是:

(property) MutableRefObject<HTMLDivElement | null>.current: HTMLDivElement
The ref value 'containerRef.current' will likely have changed by the time this effect cleanup function runs. If this ref points to a node rendered by React, copy 'containerRef.current' to a variable inside the effect, and use that variable in the cleanup function.

eslint react-hooks/exhaustive-deps eslint react-hooks/exhaustive-deps

2. What I tried to do to remove the warning is to save the current ref value to a locally scoped variable to be closed over in the function. 2. 我试图删除警告的方法是将当前 ref 值保存到本地范围的变量中,以便在函数中关闭。

const useElementOnScreen = <T,>(options: T): [MutableRefObject<HTMLDivElement | null>, boolean] => {
    let observerRefValue: Element | null = null; // <-- variable to hold ref value
    const containerRef = useRef<HTMLDivElement | null>(null);
    const [isVisible, setIsVisible] = useState(false);

    const callbackFunction = (entries: IntersectionObserverEntry[]) => {
        const [entry] = entries;
        setIsVisible(entry.isIntersecting);
    };

    useEffect(() => {
        const observer = new IntersectionObserver(callbackFunction, options);

        if (containerRef.current) observer.observe(containerRef?.current);
        observerRefValue = containerRef.current; // <-- save ref value

        return () => {
            if (observerRefValue) observer.unobserve(observerRefValue); // <-- use saved value
        };
    }, [containerRef, options]);

    return [containerRef, isVisible];
};

But then again here I am getting the warning: observerRefValue = containerRef.current; // <-- save ref value但话又说回来,我收到警告: observerRefValue = containerRef.current; // <-- save ref value observerRefValue = containerRef.current; // <-- save ref value

(property) MutableRefObject<HTMLDivElement | null>.current: HTMLDivElement | null
Assignments to the 'observerRefValue' variable from inside React Hook useEffect will be lost after each render. To preserve the value over time, store it in a useRef Hook and keep the mutable value in the '.current' property. Otherwise, you can move this variable directly inside useEffect.eslintreact-hooks/exhaustive-deps

3. Now I changed my let observerRefValue: Element | null = null; 3. 现在我改变了我的let observerRefValue: Element | null = null; let observerRefValue: Element | null = null; to const [observerRefValue, setObserverRefValue] = useState<Element | null>(null);const [observerRefValue, setObserverRefValue] = useState<Element | null>(null); const [observerRefValue, setObserverRefValue] = useState<Element | null>(null); And the warning is gone and its working!警告消失了,它的工作! However in the current state I am not using setObserverRefValue anywhere and I am getting the warning 'setObserverRefValue' is assigned a value but never used .但是,在当前状态下,我没有在任何地方使用setObserverRefValue ,并且收到警告'setObserverRefValue' is assigned a value but never used

const useElementOnScreen = <T,>(options: T): [MutableRefObject<HTMLDivElement | null>, boolean] => {
    const [observerRefValue, setObserverRefValue] = useState<Element | null>(null);
    const containerRef = useRef<HTMLDivElement | null>(null);
    const [isVisible, setIsVisible] = useState(false);

    const callbackFunction = (entries: IntersectionObserverEntry[]) => {
        const [entry] = entries;
        setIsVisible(entry.isIntersecting);
    };

    useEffect(() => {
        const observer = new IntersectionObserver(callbackFunction, options);

        if (containerRef.current) observer.observe(containerRef?.current);

        return () => {
            if (observerRefValue) observer.unobserve(observerRefValue); // <-- use saved value
        };
    }, [observerRefValue, containerRef, options]);

    return [containerRef, isVisible];
};

Its just a warning but my question is:只是一个警告,但我的问题是:

Is my solution correct to handle the previous warning?我的解决方案是否正确处理先前的警告? Or are there maybe better solutions?还是有更好的解决方案? And regarding to the 'setObserverRefValue' is assigned a value but never used is there a way to use it in my example so I can get rid of the warning?关于'setObserverRefValue' is assigned a value but never used ,有没有办法在我的示例中使用它,这样我就可以摆脱警告?

I think it could be something like我认为它可能是这样的

export function useElementOnScreen(ref: RefObject<HTMLElement>, rootMargin?: string) {
  const [isVisible, setIsVisible] = useState(false);

  useEffect(() => {
    const element = ref.current;

    if (!element) return;

    const observer = new IntersectionObserver(
      ([entry]) => {
        setIsVisible(entry.isIntersecting);
      },
      { threshold: [0, 1], rootMargin }
    );

    observer.observe(element);

    return () => {
      observer.disconnect();
    };
  }, [ref, rootMargin]);

  return isVisible;
}

This hook returns boolean indicating if element is visible or not.这个钩子返回boolean ,指示元素是否可见。

Note that the hook is not creating ref by itself - instead it expect it as an argument.请注意,钩子本身并不是创建ref ——而是期望它作为一个参数。

Usually it is a better pattern, as you might want to run multiple hooks on the same element.通常这是一种更好的模式,因为您可能希望在同一个元素上运行多个挂钩。 If each hook created independent ref - then there is no easy way to assign all of those refs to a single HTML element.如果每个钩子都创建了独立的 ref - 那么就没有简单的方法将所有这些 ref 分配给单个 HTML 元素。

Example of usage:使用示例:

export function Foo() {
  const elementRef = useRef<HTMLElement>(null);

  const isVisible = useIsVisible(elementRef);
  const isVisibleWithMargin = useIsVisible(elementRef, "100px");

  return (
    <div ref={elementRef}>
      {isVisible ? "visible" : "not visible"}
      {isVisibleWithMargin ? "visible with margin" : "not visible with margin"}
    </div>
  );
}

Note that I am saving ref.current value into const element .请注意,我将ref.current值保存到const element中。 This is because ref.current is mutable and can be cleared by react when element unmounts.这是因为ref.current是可变的,并且可以在元素卸载时通过 react 清除。 Thus we need to save it to variable so we can properly clean it up later.因此我们需要将它保存到变量中,以便我们以后可以正确地清理它。

What Adam has posted, is a nice way to solve this.亚当发布的内容是解决此问题的好方法。 I use the exact same code pattern in my code base.我在我的代码库中使用完全相同的代码模式。 The ref is not a part of the hook, and is passed as a prop. ref 不是钩子的一部分,而是作为道具传递的。 That way the hook only contains logic for checking if the Node pointed to by the ref is visible on the screen or not.这样钩子只包含用于检查 ref 指向的节点是否在屏幕上可见的逻辑。 This has worked for us uptil now.到目前为止,这对我们有用。

Now in your code, the hook takes care of the ref too.现在在你的代码中,钩子也处理了 ref。 And you then attach the ref to any value returned by the hook.然后将 ref 附加到钩子返回的任何值上。 (Would be interested in knowing comments why you are following this pattern.) (有兴趣了解为什么您遵循这种模式的评论。)

I will discuss all three code snippets you have provided:我将讨论您提供的所有三个代码片段:

  1. The useEffect使用效果
useEffect(() => {
        const observer = new IntersectionObserver(callbackFunction, options);

        if (containerRef.current) observer.observe(containerRef?.current);
        observerRefValue = containerRef.current; // <-- save ref value

        return () => {
            if (observerRefValue) observer.unobserve(observerRefValue); // <-- use saved value
        };
    }, [containerRef, options]);

Firstly, you can use containerRef as a dependency, but let me remind you that changing containerRef.current will not trigger a render.首先,您可以使用containerRef作为依赖项,但让我提醒您,更改containerRef.current不会触发渲染。 It is not a state variable and does not contribute to rerender.它不是状态变量,对重新渲染没有贡献。 So if the ref changes, you cannot be sure that your useEffect callback will run again and hence you might have wrong values in your ref container.因此,如果 ref 更改,您无法确定您的useEffect回调是否会再次运行,因此您的 ref 容器中可能有错误的值。

The linting warning has the same concern: linting 警告也有同样的问题:

The ref value 'containerRef.current' will likely have changed by the time this effect cleanup function runs.到此效果清理功能运行时,参考值“containerRef.current”可能已更改。 If this ref points to a node rendered by React, copy 'containerRef.current' to a variable inside the effect, and use that variable in the cleanup function.如果此 ref 指向 React 渲染的节点,请将“containerRef.current”复制到效果内的变量中,并在清理函数中使用该变量。

Here is a link with a similar discussion.这是一个类似讨论的链接 This code sandbox from the dicussion is a great demo.来自讨论的这个代码沙箱是一个很棒的演示。

function Box() {
  const ref = useRef();

  useEffect(() => {
    console.log("mount 1 ", ref.current);
    return () => setTimeout(() => console.log("unmount 1 ", ref.current), 0);
  }, []);

  useEffect(() => {
    const element = ref.current;

    console.log("mount 2 ", element);
    return () => setTimeout(() => console.log("unmount 2 ", element), 0);
  }, []);

  return (
    <div ref={ref} className="box">
      Box
    </div>
  );
}

export default function App() {
  let [state, setState] = useState(true);

  useEffect(() => {
    setTimeout(() => setState(false), 1000);
  }, []);

  return (
    <div className="App">
      <p>useEffect useRef warning</p>
      {state && <Box />}
    </div>
  );
}

The output for the first useEffect return function will be unmount 1 null , because value has changed, but the effect callback is unaware.第一个useEffect返回函数的输出将是unmount 1 null ,因为值已更改,但效果回调不知道。

Similar DOM manipulations can happen in your code too.类似的 DOM 操作也可能发生在您的代码中。 Now that we acknowledge the warning makes sense, let us first get to the solution:既然我们承认警告是有道理的,让我们首先找到解决方案:

The simple solution:简单的解决方案:

const useOnScreen = <T,>(options: T): [MutableRefObject<HTMLDivElement | null>, boolean] => {
  const containerRef = useRef<HTMLDivElement | null>(null);
  const [isVisible, setIsVisible] = useState(false);

  const callbackFunction = (entries: IntersectionObserverEntry[]) => {
    const [entry] = entries;
    setIsVisible(entry.isIntersecting);
  };

  useEffect(() => {
    const observer = new IntersectionObserver(callbackFunction, options);
    const refCopy = containerRef.current;
    if (refCopy) observer.observe(refCopy);

    return () => {
      if (refCopy) observer.unobserve(refCopy);
    };
  }, [options]);

  return [containerRef, isVisible];
};

The above code solves all warnings.上面的代码解决了所有警告。 This is exactly what the linter suggested.这正是 linter 所建议的。 The ref points to a DOM Element , which on unmount is null , but by keeping a copy of the variable, we can still access it and do whatever we want with it. ref 指向一个DOM Element ,它在卸载时是null ,但是通过保留变量的副本,我们仍然可以访问它并使用它做任何我们想做的事情。

Coming to your other attempts:来到你的其他尝试:

  1. observerRefValue is a non-special variable (not a ref variable nor a state variable) here, and the changes made to it will be cleared on each render. observerRefValue在这里是一个非特殊变量(既不是 ref 变量也不是 state 变量),对它所做的更改将在每次渲染时被清除。 So you cannot rely on its value.所以你不能依赖它的价值。 After every rednder it gets assigned the value null as defined initially.在每次 rednder 之后,它都会被赋予最初定义的null值。 Hence the warning.因此发出警告。 This also might not help your use case.这也可能对您的用例没有帮助。

  2. You are creating an extra state variable here (not required).您在这里创建了一个额外的状态变量(不是必需的)。 It is initialized to null but never updated to any other value.它被初始化为null但从未更新为任何其他值。 The below condition will always be false and your intended code will not run only:以下条件将始终为假,您的预期代码将不会仅运行:

if (observerRefValue)

This will also not help your use case.这也无助于您的用例。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 为什么我收到此错误:React Hook useEffect 内部的变量在每次渲染后都会丢失? - Why i am getting this error: variable from inside React Hook useEffect will be lost after each render? 如何修复:每次渲染后,React Hook useEffect 中对“主题”变量的赋值将丢失 - How to fix: Assignments to the 'theme' variable from inside React Hook useEffect will be lost after each render 每次渲染后,从 React Hook useEffect 内部对 'timeInterval' 变量的分配将丢失 - Assignments to the 'timeInterval' variable from inside React Hook useEffect will be lost after each render 修改 useEffect() 中使用 fetch() 调用的 React 挂钩变量 - Modify a React hook variable inside useEffect() that uses a fetch() call 使 React useEffect 钩子不在初始渲染上运行 - Make React useEffect hook not run on initial render React useEffect Hook 不会在具有 [] 依赖项的第一次渲染时触发 - React useEffect Hook not Triggering on First Render with [] Dependencies 导航到下一页后,React Hook useEffect 不会在挂载时呈现(React Native) - React Hook useEffect does not render on mount, after navigating to next page (React Native) React Hook useEffect() - 在 mapStateToProps 之后运行 - React Hook useEffect() - Run after mapStateToProps 如何将子集合中的数据分配到其集合并呈现新对象? 使用 react、firebase 和 useeffect-hook - How to assign data from a subcollection to its collection and render the new object? Using react, firebase and useeffect-hook 为什么每次渲染后都会执行useEffect? - why is useEffect executing after each render?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM