반응형

늦은감이 있지만 리액트 스터디를 재시작했습니다.

 

언어를 습득하는데 작은 프로젝트라도 해보는게 머리에 잘 남기에 개인 이력이 담긴 포트폴리오를 작성하고 깃허브에 올리는 프로젝트를 수행 중이였습니다.

 

여러가지 최대한 아는 기능선에서 만들어보고 있었는데, 자기 소개를 하는 페이지를 작성하는 부분의 텍스트를 실시간으로 타이핑하는 효과를 주기 위해 이미 작성 된 데이터를 담아둔 배열을 setInterval을 통해 표현하면 될 거라고 생각하고 작성을 시작했습니다.

 

useEffect를 통해 로드 시 한번만 setInterval이 동작하도록 하였고, 생각보다 많은 시행착오를 겪고 나서 처리까지 완료가 되었습니다.

 

아래는 처음 작성해 본 예제입니다.

Introduce.js

import { useState, useEffect } from "react";
import {MdKeyboardReturn} from  "react-icons/md";
import "./Introduce.scss";
import bg_img from "../../../assets/imgs/monet.jpg";

const Introduce = () => {
    const OPTION = {
        writeArr: [],
        arrIndex: 0,
        index: 0
    };
    const WORD_TYPING_SPEED = 80;
    const msgArr = [
        " 안녕하세요? 웹 개발자 박신우입니다.",
        " 저를 소개하기 위한 페이지를 만들었습니다.",
        " 저의 정보를 확인해주세요.",
    ];

    const [introduce, setIntroduce] = useState([]);

    const onChangeMsg = (o) => {
        const msg = msgArr[o.arrIndex];
        o.writeArr[o.arrIndex] = msg.substring(0, ++o.index);
        if(o.index > msg.length) {
            o.index = 0;
            if(++o.arrIndex >= msgArr.length){
                o.arrIndex = 0;
                o.writeArr.splice(0, o.writeArr.length);
            }
        }
        setIntroduce(() => [...o.writeArr]);
    };

    useEffect(()=>{
        const interval = setInterval(()=>{
            onChangeMsg(OPTION);
        }, WORD_TYPING_SPEED);
        return () => clearInterval(interval);
    }, []);
    
    return (
        <div id="introduce" className="Introduce" style={{background: `url(${bg_img}) no-repeat`}}>
            <h2 className="introduce_msg">
                {
                    introduce.map((m, i) => (
                        msgArr[i].length === m.length ? <p key={i}>{m}</p>
                        : <p key={i}>{m}<MdKeyboardReturn /></p>                    
                    ))
                }
            </h2>
        </div>
    );
}

export default Introduce;

나름 UI를 확인해봤을땐 별 문제 없이 잘 동작하는것으로 보였습니다.

 

하지만, 관리자 모드를 열어서 확인해보니 Hook관련 에러가 계속해서 발생하는 것을 확인했습니다.😣

 

처리 방법

검색을 하다보니 setInterval과 react의 동작 구조 개념에서 발생하는 현상이라는 번역 글을 확인했습니다.

 

아래는 제가 참고한 블로그 글입니다.

https://velog.io/@jakeseo_me/%EB%B2%88%EC%97%AD-%EB%A6%AC%EC%95%A1%ED%8A%B8-%ED%9B%85%EC%8A%A4-%EC%BB%B4%ED%8F%AC%EB%84%8C%ED%8A%B8%EC%97%90%EC%84%9C-setInterval-%EC%82%AC%EC%9A%A9-%EC%8B%9C%EC%9D%98-%EB%AC%B8%EC%A0%9C%EC%A0%90

 

번역 / 리액트 훅스 컴포넌트에서 setInterval 사용 시의 문제점

Dan abramov의 https://overreacted.io/making-setinterval-declarative-with-react-hooks/ 번역입니다.All copyrights to Dan Abramovtranslated by Jake seoTHE

velog.io

 

setInterval대신 커스텀으로 작성된 컴포넌트 useInterval을 사용하라는 간단한 제안이 되어있습니다.

 

별도로 컴포넌트를 작성후 import하여 해결하였고, 그 결과는 아래 소스입니다.

useInterval.js

import { useEffect, useRef } from 'react';

const useInterval = (callback, delay) => {
  const savedCallback = useRef();

  // Remember the latest callback.
  useEffect(() => {
    savedCallback.current = callback;
  }, [callback]);

  // Set up the interval.
  useEffect(() => {
    function tick() {
      savedCallback.current();
    }
    if (delay !== null) {
      let id = setInterval(tick, delay);
      return () => clearInterval(id);
    }
  }, [delay]);
}

export default useInterval;

 

Introduce.js

import { useState } from "react";
import useInterval from "../../common/useInterval";
import {MdKeyboardReturn} from  "react-icons/md";
import "./Introduce.scss";
import bg_img from "../../../assets/imgs/monet.jpg";


const OPTION = {
    writeArr: [],
    arrIndex: 0,
    index: 0
};
const WORD_TYPING_SPEED = 80;
const msgArr = [
    " 안녕하세요? 웹 개발자 박신우입니다.",
    " 저를 소개하기 위한 페이지를 만들었습니다.",
    " 저의 정보를 확인해주세요.",
];

const Introduce = () => {
    const [introduce, setIntroduce] = useState([]);

    const onChangeMsg = (o) => {
        const msg = msgArr[o.arrIndex];
        o.writeArr[o.arrIndex] = msg.substring(0, ++o.index);
        if(o.index > msg.length) {
            o.index = 0;
            if(++o.arrIndex >= msgArr.length){
                o.arrIndex = 0;
                o.writeArr.splice(0, o.writeArr.length);
            }
        }
        setIntroduce(() => [...o.writeArr]);
    };

    useInterval(() => {
        onChangeMsg(OPTION);
    }, WORD_TYPING_SPEED);

    return (
        <div id="introduce" className="Introduce" style={{background: `url(${bg_img}) no-repeat`}}>
            <h2 className="introduce_msg">
                {
                    introduce.map((m, i) => (
                        msgArr[i].length === m.length ? <p key={i}>{m}</p>
                        : <p key={i}>{m}<MdKeyboardReturn /></p>                    
                    ))
                }
            </h2>
        </div>
    );
}

export default Introduce;

소스도 한결 가벼워지고 더 이상 오류문구도 발생하지 않는걸 볼 수 있습니다.

useInterval에서 useEffect를 통해 setInterval을 처리하기 때문에 useEffect로 감싸지 않는것을 볼 수 있습니다.

반응형