简体   繁体   English

Typescript 中的 Intersection Observer 在 useRef 中抛出错误

[英]Intersection Observer in Typescript throws error in useRef

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的新钩子中实现了逻辑

But Typescript is telling me an error at containerRef?.current :但是 Typescript 在containerRef?.current告诉我一个错误:

Argument of type 'IntersectionObserver' is not assignable to parameter of type 'Element'.
  Type 'IntersectionObserver' is missing the following properties from type 'Element': attributes, classList, className, clientHeight, and 160 more.

And I am not sure how to solve this error.而且我不确定如何解决这个错误。 I think this is also the reason that my ref={containerRef} is throwing an error too我认为这也是我的ref={containerRef}也抛出错误的原因

The expected type comes from property 'ref' which is declared here on type 'IntrinsicAttributes & { component: ElementType<any>; } & SystemProps<Theme> & { children?: ReactNode; component?: ElementType<...> | undefined; ref?: Ref<...> | undefined; sx?: SxProps<...> | undefined; } & CommonProps & Omit<...>'

The animation: So, TopAnimateBlock and BottomAnimateBlock have numOfLine property hence how many lines is inside the block.动画:因此,TopAnimateBlock 和 BottomAnimateBlock 具有 numOfLine 属性,因此块内有多少行。 The second property in BottomAnimateBlock is delayTopLine, it should have the same numbers as a numOfLine in TopAnimateBlock, because we need to wait for top lines to play. BottomAnimateBlock 中的第二个属性是 delayTopLine,它应该与 TopAnimateBlock 中的 numOfLine 具有相同的数字,因为我们需要等待顶行播放。

TextAnimation.tsx

import { Box, Stack, Typography } from '@mui/material';
import React, { useRef, useEffect, useState } from 'react';
import styled, { keyframes } from 'styled-components';

const showTopText = keyframes`
  0% { transform: translate3d(0, 100% , 0); }
  40%, 60% { transform: translate3d(0, 50%, 0); }
  100% { transform: translate3d(0, 0, 0); }
`;
const showBottomText = keyframes`
  0% { transform: translate3d(0, -100%, 0); }
  100% { transform: translate3d(0, 0, 0); }
`;

const Section = styled.section`
  width: calc(100% + 10vmin);
  display: flex;
  flex-flow: column;
  padding: 2vmin 0;
  overflow: hidden;
  &:last-child {
    border-top: 1vmin solid white;
  }
`;

const Block = styled.div<{ numOfLine: number }>`
  position: relative;
`;
const TopAnimateBlock = styled(Block)`
  animation: ${showTopText} calc(0.5s * ${props => props.numOfLine}) forwards;
  animation-delay: 0.5s;
  transform: translateY(calc(100% * ${props => props.numOfLine}));
`;
const BottomAnimateBlock = styled(Block)<{ delayTopLine: number }>`
  animation: ${showBottomText} calc(0.5s * ${props => props.numOfLine}) forwards;
  animation-delay: calc(0.7s * ${props => props.delayTopLine});
  transform: translateY(calc(-100% * ${props => props.numOfLine}));
`;

const TextStyle = styled.p<{ color: string }>`
  font-family: Roboto, Arial, sans-serif;
  font-size: 12vmin;
  color: ${props => props.color};
`;


const useElementOnScreen = (options) => {
    const containerRef = useRef<IntersectionObserver | null>(null);
    const [isVisible, setIsVisible] = useState(false);

    const callbackFunction = (entries) => {
        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];
};

export function Details() {
    const [containerRef, isVisible] = useElementOnScreen({
        root: null,
        rootMargin: '0px',
        threshold: 1.0,
    });

    return (
    <>
    <Typography>Scroll Down</Typography>
     <Box ref={containerRef}>
      <Section>
        <TopAnimateBlock numOfLine={2}>
          <TextStyle color="grey">mimicking</TextStyle>
          <TextStyle color="white">apple's design</TextStyle>
        </TopAnimateBlock>
      </Section>
      <Section>
        <BottomAnimateBlock numOfLine={1} delayTopLine={2}>
          <TextStyle color="white">for the win!</TextStyle>
        </BottomAnimateBlock>
      </Section>
    </Box>
</>
  );
};

I can broadly find 2 issues in the code:我可以在代码中大致找到 2 个问题:

First is this statement:首先是这个声明:

 const containerRef = useRef<IntersectionObserver | null>(null);

The implementation of the generic useRef is being done with IntersectionObserver | null通用useRef的实现是通过IntersectionObserver | null完成的。 IntersectionObserver | null . IntersectionObserver | null This means that the ref container will hold either an instance of IntersectionObserver or null .这意味着 ref 容器将保存IntersectionObservernull的实例。 But instead the ref is being assigned to a Box , (for those not versed with material-UI, this will be a div ) .但是 ref 被分配给Box ,(对于那些不熟悉 material-UI 的人,这将是一个div )。 So that should be changed to something like:所以应该改为:

  const containerRef = useRef<HTMLDivElement | null>(null);

Second, the return type of the hook is not declared and since it is an array [containerRef, isVisible] , TS auto detects it.其次,没有声明钩子的返回类型,并且由于它是一个数组[containerRef, isVisible] ,因此 TS 会自动检测到它。 The type inferred by Typescript becomes: Typescript 推断的类型变为:

钩子的打字稿返回类型

(boolean | React.MutableRefObject<HTMLDivElement | null>)[] . (boolean | React.MutableRefObject<HTMLDivElement | null>)[]

Since the type is actually a tuple and both the returned array elements are of different types, the type inferred is incorrect and TS complains.由于类型实际上是一个元组,并且返回的两个数组元素都是不同的类型,所以推断的类型不正确,TS 会抱怨。

Declaring this while defining the hook would prevent Typescript from complaining.在定义钩子时声明这一点可以防止 Typescript 抱怨。

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

  const callbackFunction = (entries) => {
    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];
};

A link explaining the difference between tuple return type and array return type解释元组返回类型和数组返回类型之间区别的链接

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

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM