반응 애니메이션 onDestroy(DOM에서 제거됨)

안녕하세요. DOM에서 제거된 구성 요소가 소멸될 때 React의 전환 애니메이션에 대한 이 게시물에 오신 것을 환영합니다.

저는 일반적으로 React 구성 요소 또는 JSX 요소에 일부 fadeIn/fadeOut 전환을 적용할 때 문제에 직면합니다. 구성 요소/요소가 DOM에서 제거되면 더 이상 전환을 적용할 수 없기 때문입니다.

매초 1씩 증가하는 카운터가 있는 기본적인 React 앱을 작성해 봅시다.
카운터를 표시하고 그 아래에 카운트가 3으로 나누어질 때마다 반응 이미지를 표시합니다.

데모here .

// Disable React Strict mode for this example
function App() {
  const [count, setCount] = useState(0);
  const [isDivisibleByThree, setIsDivisibleByThree] = useState(false);

  useEffect(() => {
    if (count !== 0 && count % 3 === 0) setIsDivisibleByThree(true);
  }, [count]);

  useEffect(() => {
    setInterval(() => {
      setCount((p) => (p += 1));
    }, 1000);
  }, []);

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "50px",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <div>
        <h1>count is {count}</h1>
      </div>
      <p>
        {isDivisibleByThree && (
          <img ref={imageRef} src={reactLogo} alt="react logo"/>
        )}
      </p>
    </div>
  );
}


다음 CSS를 사용하여 <img>가 표시될 때마다 애니메이션을 적용할 수 있습니다.
이렇게 하면 이미지가 처음 나타날 때 슬라이드 인 애니메이션이 위에서 아래로 이동합니다.

img {
  animation: slide-in 0.5s ease-in-out;
}
p {
  height: 5rem;
  width: 5rem;
}
@keyframes slide-in {
  0% {
    opacity: 0;
    transform: translateY(-50%);
  }
  100% {
    opacity: 1;
    transform: translateY(0);
  }
}


그래서... <img> 요소가 DOM에서 제거될 때 어떻게 애니메이션을 적용할 수 있습니까?
useHandleDestroyAnimated라는 사용자 정의 후크를 생성해 보겠습니다. 이 후크는 제거 시 애니메이션을 적용하려는 HTML 요소의 참조(useRef 후크에서)를 허용합니다.

isDivisibleByThree 상태를 false로 설정하여 <img>를 숨기기 전에 1000ms 후에 소멸을 애니메이션한 다음 상태를 false로 설정합니다.

async function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

function useHandleDestroyAnimated<T extends HTMLElement>(
  ref: MutableRefObject<T | null>
): [boolean, (_: boolean) => void] {
  const [state, setState] = useState(false);

// everytime the state or ref change, if state is true, we animate the destroy of the component.
  useEffect(() => {
    if (state) {
      handleDeletion(ref);
    }
  }, [ref, state]);

  function handleDeletion<T extends HTMLElement>(
    element: MutableRefObject<T | null>
  ) {
    const style = element?.current?.style;
    if (!style) return;
    sleep(1000).then(() => {
      style.transition = "all 0.5s";
      style.transform = "translateY(-50%)";
      style.opacity = "0";
      sleep(1000).then(() => {
        setState(false);
      });
    });
  }
  return [state, setState];
}

useRef 후크의 ref를 App.tsx에 추가해 보겠습니다.

const imageRef = useRef<HTMLImageElement | null>(null);
.....
.....
.....
 <p>
        {isDivisibleByThree && (
          <img ref={imageRef} src={reactLogo} alt="react logo" />
        )}
      </p>


최종 코드는 다음과 같습니다.

import { useEffect, useState, MutableRefObject, useRef } from "react";
import reactLogo from "./assets/react.svg";
import "./App.css";

async function sleep(ms: number) {
  return new Promise((resolve) => setTimeout(resolve, ms));
}

const styles = Object.freeze({
  opacity: "0",
  transform: "translateY(-50%)",
  transition: "all 0.5s",
});

function useHandleDestroyAnimated<T extends HTMLElement>(
  ref: MutableRefObject<T | null>
): [boolean, (_: boolean) => void] {
  const [state, setState] = useState(false);

  useEffect(() => {
    if (state) {
      handleDeletion(ref);
    }
  }, [ref, state]);

  function handleDeletion<T extends HTMLElement>(
    element: MutableRefObject<T | null>
  ) {
    const style = element?.current?.style;
    if (!style) return;
    sleep(1000).then(() => {
      style.transition = styles.transition;
      style.transform = styles.transform;
      style.opacity = styles.opacity;
      sleep(1000).then(() => {
        setState(false);
      });
    });
  }
  return [state, setState];
}

function App() {
  const [count, setCount] = useState(0);
  const imageRef = useRef<HTMLImageElement | null>(null);
  const [isDivisibleByThree, setIsDivisibleByThree] =
    useHandleDestroyAnimated(imageRef);

  useEffect(() => {
    if (count !== 0 && count % 3 === 0) setIsDivisibleByThree(true);
  }, [count]);

  useEffect(() => {
    setInterval(() => {
      setCount((p) => (p += 1));
    }, 1000);
  }, []);

  return (
    <div
      style={{
        display: "flex",
        flexDirection: "column",
        gap: "50px",
        alignItems: "center",
        justifyContent: "center",
      }}
    >
      <div>
        <span>count is {count}</span>
      </div>
      <p>
        {isDivisibleByThree && (
          <img ref={imageRef} src={reactLogo} alt="react logo" />
        )}
      </p>
    </div>
  );
}

export default App;


이 정보가 도움이 되었기를 바랍니다.

다음에서 나를 팔로우할 수 있습니다.
  • Github
  • 좋은 웹페이지 즐겨찾기