[React] 타이핑효과 setTimeout

gigi·2023년 5월 15일
0

커스텀훅으로 useTypingEffect 만들기

import { useState, useEffect } from "react";

function useTypingEffect(text, delay) {
  const [typedText, setTypedText] = useState("");

  useEffect(() => {
    let currentIndex = 0;
    const intervalId = setInterval(() => {
      const currentLetter = text[currentIndex];
      setTypedText((prevTypedText) => prevTypedText + currentLetter);
      currentIndex++;
      if (currentIndex === text.length) {
        clearInterval(intervalId);
      }
    }, delay);

    return () => {
      clearInterval(intervalId);
      setTypedText("");
    };
  }, [text, delay]);

  return typedText;
}

export default useTypingEffect;

text와 delay를 전달받고 setInterval을 이용해 delay 마다 빈문자열에 text를 한글자씩 붙여주는 방식이다.


setState 함수의 인자에 콜백함수 사용

setState((prevState) => {
	return()
});

useState 세터 함수의 인자로 콜백함수를 넣어주게 되면

  1. 콜백함수의 인자는 이전 상태값을 가지게 된다.
  2. 콜백함수의 return 값에는 새로운 state를 지정할 수 있다.

콜백 형식의 useState 초기값 지정

useState(() => {
	return()
});

useState를 콜백 형식으로 초기값을 지정하면 첫 렌더링 시에만 한 번 콜백을 실행해서 초기값을 만들고, 그 이후에는 콜백함수를 실행하지 않는다.

따라서 useState 초기값 지정시 오래걸리는 작업이 있을때 사용해주면 좋다.
단, 콜백 함수가 리턴할 때까지 React가 렌더링을 하지 않고 기다리게 된다.


사용예시

const TerminalLine = React.memo(function TerminalLine({ text }) {
  let typingText = useTypingEffect(text, 60);

  return (
    <div>
      {typingText}
    </div>
  );
});

export default TerminalLine;

나의 경우 타이핑을 한번만 하고 끝내는것이 아니라 여러문장을 나타내야했는데 이전 문장이 완료되는 시점에 다음줄의 타이핑이 시작되어야했다.
그렇기 때문에 useTypingEffect을 이용해 첫번째 타이핑이 끝나는 시간을 다음줄의 시작 시간으로 설정하고 그 다음줄은 이전 모든 줄의 타이핑이 끝나는 시간으로 시작 시간을 설정한다.


  useEffect(() => {
    let timeoutId;
    const arrTimeoutId = [];

    const startTyping = () => {
      let delay = 500;
      for (let i = 0; i < text.length; i++) {
        const textLine = text[i];
        timeoutId = setTimeout(() => {
          if (i > 0) {
            // 완료된 이전 문장들을 담을 state
            setCompleteTyping(text.slice(0, i));
          }
          // useTypingEffect에 전달할 text state
          setTypingText(textLine);
        }, delay);
        
        arrTimeoutId.push(timeoutId);
		// 이전 문장의 길이 * 타이핑 속도에 여유시간(500ms)를 더해 다음 타이핑 시작 시간을 조절한다.
        delay += text[i].length * 60 + 500;
      }
    };


    startTyping();


    return () => {
      // clearTimeout(timeoutId);
      arrTimeoutId.forEach((id) => clearTimeout(id));
    };
  }, [text]);

참고
setTimeout을 이렇게 여러번 사용해본적이 처음이라 clear를 잘못하고 있었다는걸 몰랐었다. 타이핑 이펙트 주는 화면이 여러개라 전환을 하면 이전 화면 텍스트가 잠깐 나타났다가 사라지곤 했는데 마지막 주석처리 한것처럼 컴포넌트 언마운트 시 타이머를 클리어 하도록 timeoutId 하나만 clearTimeout 함수의 인자로 전달하면 제일 마지막 setTimeout만 클리어된다.(for문에서 setTimeout 호출할때마다 반환하는 id를 timeoudId 변수에 할당하는데 for문이 끝나고 난 다음에는 제일 마지막 id만 할당되어있는 상태다.)
setTimeout을 호출하면 타이머를 식별하는 id값을 반환하는데 실행 대기중인 모든 타이머 id를 clear해줘야한다.

0개의 댓글