【问题标题】:IntersectionObserver with React & Hooks带有 React 和 Hooks 的 IntersectionObserver
【发布时间】:2019-10-11 12:58:44
【问题描述】:

我正在尝试使用 React/Hooks 和 Intersection Observer API 跟踪元素可见性。但是,我不知道如何使用“useEffect”设置观察。有谁知道我该怎么做?我的解决方案不起作用...

function MainProvider({ children }) {
  const [targetToObserve, setTargetToObserve] = useState([]);

  window.addEventListener("load", () => {
    const findTarget = document.querySelector("#thirdItem");
    setTargetToObserve([findTarget]);
  });

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        if (entry.intersectionRatio === 0.1) {
          console.log("It works!");
        }
      },
      {
        root: null,
        rootMargin: "0px",
        threshold: 0.1
      }
    );
    if (targetToObserve.current) {
      observer.observe(targetToObserve.current);
    }
  }, []);

  return (
    <main>
     <div className="Section-item" id="firstItem"></div>
     <div className="Section-item" id="secondItem"></div>
     <div className="Section-item" id="thirdItem"></div>
    </main>
  );
}

【问题讨论】:

标签: javascript reactjs react-hooks intersection-observer


【解决方案1】:

需要使用 React.useRef() 而不是 addEventListener('load', function() ),因为 eventListener 会在屏幕上出现某些内容之前运行。

import React, { useRef, useEffect } from 'react'

function MainProvider({ children }) {
  const ref = useRef();

  useEffect(() => {
    const observer = new IntersectionObserver(
      ([entry]) => {
        console.log(entry);

        if (entry.isIntersecting) {
          //do your actions here
          console.log('It works!')
        }
      },
      {
        root: null,
        rootMargin: "0px",
        threshold: 0.1
      }
    );
    if (ref.current) {
      observer.observe(ref.current);
    }
  }, [ref]);

  return (
    <main>
     <div className="Section-item" id="firstItem"></div>
     <div className="Section-item" ref={ref} id="secondItem"></div>
     <div className="Section-item" id="thirdItem"></div>
    </main>
  );
}

【讨论】:

    【解决方案2】:

    这是一个可重用的钩子,它使用ref 和useEffect 清理功能来防止安装/卸载大量组件时的内存泄漏

    钩子

    function useOnScreen(ref) {
    
      const [isIntersecting, setIntersecting] = useState(false)
    
      const observer = new IntersectionObserver(
        ([entry]) => setIntersecting(entry.isIntersecting)
      )
    
      useEffect(() => {
        observer.observe(ref.current)
        return () => {
          observer.disconnect()
        }
      }, [])
    
      return isIntersecting
    }
    

    在组件中的使用

    function DumbComponent() {
    
      const ref = useRef()
    
      const onScreen = useOnScreen(ref)
    
      return <div ref={ref}>{onScreen && "I'm on screen!"}</div>
    }
    

    【讨论】:

    • 我相信这个实现会在每次渲染时创建一个新的 IntersectionObserver。请参阅答案stackoverflow.com/a/67826055/1727807 以了解避免这种情况的方法。
    • 在这个钩子中使用 useMemo 相当简单: const observer = useMemo(() => new IntersectionObserver(...), [ref, rootMargin])。这是一个独立的话题,何时使用 useMemo 来防止额外处理的阈值实际上取决于您的实现
    • @Creaforge 这里的 rootMargin 是什么?
    • @TusharShahi 如果你想添加一个例子:new IntersectionObserver( ([entry]) =&gt; setIntersecting(entry.isIntersecting), { rootMargin: '50vh' })
    【解决方案3】:

    JavaScript

    挂钩

    import { useEffect, useState, useRef } from 'react';
    
    export function useOnScreen(ref) {
      const [isOnScreen, setIsOnScreen] = useState(false);
      const observerRef = useRef(null);
    
      useEffect(() => {
        observerRef.current = new IntersectionObserver(([entry]) =>
          setIsOnScreen(entry.isIntersecting)
        );
      }, []);
    
      useEffect(() => {
        observerRef.current.observe(ref.current);
    
        return () => {
          observerRef.current.disconnect();
        };
      }, [ref]);
    
      return isOnScreen;
    }
    
    
    

    用法:

    import { useRef } from 'react';
    import useOnScreen from './useOnScreen';
    
    function MyComponent() {
      const elementRef = useRef(null);
      const isOnScreen = useOnScreen(elementRef);
    
      console.log({isOnScreen});
    
      return (
        <div>
          <div style={{ paddingBottom: '140vh' }}>scroll to element...</div>
          <div ref={elementRef}>my element</div>
        </div>
      );
    }
    
    

    打字稿

    挂钩

    import { useEffect, useState, useRef, RefObject } from 'react';
    
    export default function useOnScreen(ref: RefObject<HTMLElement>) {
      const observerRef = useRef<IntersectionObserver | null>(null);
      const [isOnScreen, setIsOnScreen] = useState(false);
    
      useEffect(() => {
        observerRef.current = new IntersectionObserver(([entry]) =>
          setIsOnScreen(entry.isIntersecting)
        );
      }, []);
    
      useEffect(() => {
        observerRef.current.observe(ref.current);
    
        return () => {
          observerRef.current.disconnect();
        };
      }, [ref]);
    
      return isOnScreen;
    }
    
    

    用法:

    import { useRef } from 'react';
    import useOnScreen from './useOnScreen';
    
    function MyComponent() {
      const elementRef = useRef<HTMLDivElement>(null);
      const isOnScreen = useOnScreen(elementRef);
    
      console.log({isOnScreen});
    
      return (
        <div>
          <div style={{ paddingBottom: '140vh' }}>scroll to element...</div>
          <div ref={elementRef}>my element</div>
        </div>
      );
    }
    
    

    https://codesandbox.io/s/useonscreen-uodb1?file=/src/useOnScreen.ts

    【讨论】:

    • 很好的例子,但我收到了 TS 警告:Cannot assign to 'current' because it is a read-only property。如果我将|null 添加到useRef 调用(即useRef&lt;IntersectionObserver|null&gt;(null);)它就会消失,不确定这是否是正确的解决方案。
    • 我改了。感谢您的反馈意见。在@types/react 中是使用说明:` * ...如果您需要useRef 的结果直接可变,请包含'| null' 在泛型参数的类型 * 中。`
    【解决方案4】:

    从您的示例来看,您似乎只需要在初始渲染时设置一次观察者。

    function MainProvider({ children }) {
      useEffect(() => {
        const observer = new IntersectionObserver(
          ([entry]) => {
            if (entry.intersectionRatio === 0.1) {
              console.log("It works!");
            }
          },
          {
            root: null,
            rootMargin: "0px",
            threshold: 0.1
          }
        );
    
        const findTarget = document.querySelector("#thirdItem");
    
        if (findTarget) {
          observer.observe(targetToObserve.current);
        }
      }, []);
    
      return (
        <main>
         <div className="Section-item" id="firstItem"></div>
         <div className="Section-item" id="secondItem"></div>
         <div className="Section-item" id="thirdItem"></div>
        </main>
      );
    }
    

    但是,如果您有其他依赖项需要您添加或删除更多您正在观察的元素,您可以将您的观察者放在 useEffect 挂钩中,确保包含您正在尝试的事物的依赖项观察。

    如果你也把你的观察者放在你的依赖数组中(正如你的 linter 可能建议的那样),你会得到一个有用的错误消息,告诉你每次渲染都会创建一个新的观察者对象,触发这个钩子运行每次渲染。相反,它建议您将观察者置于 useMemo 挂钩中,建议将其用于昂贵的计算。

    function MainProvider({ children }) {
      const observer = useMemo(() => return new IntersectionObserver(
        ([entry]) => {
          if (entry.intersectionRatio === 0.1) {
            console.log("It works!");
          }
        },
        {
          root: null,
          rootMargin: "0px",
          threshold: 0.1
        }
      );
     );
    
      useEffect(() => {
        const findTarget = document.querySelector("#thirdItem");
    
        if (targetToObserve.current) {
          observer.observe(targetToObserve.current);
        }
      }, [observer]);
    
      return (
        <main>
         <div className="Section-item" id="firstItem"></div>
         <div className="Section-item" id="secondItem"></div>
         <div className="Section-item" id="thirdItem"></div>
        </main>
      );
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-01-06
      • 1970-01-01
      • 2019-10-23
      • 2020-05-15
      • 1970-01-01
      • 1970-01-01
      • 2023-03-10
      相关资源
      最近更新 更多