[React JS]Jquery fadeIn/Out javascript로 변경하기

2023. 12. 18. 18:45React JS

  useEffect(() => {
    $(`#${id}`).hide();
  }, []);

  const fadeIn = () => {
    $(`#${id}`).show(() => {
      $(`#${id}`).fadeIn();
    });
  };
  const fadeOut = () => {
    $(`#${id}`).fadeOut(() => {
      setMessages?.(defaultList);
    });
  };

 

 

이렇게 되어있는 컴포넌트가 있다.

아주 잘 동작하는 구문이지만 한가지 걸리는게 있었다.

현재 npm에서 jquery 라이브러리를 설치해서 사용하고 있는데 이 프로젝트 내에서 jquery라이브러리를 이 컴포넌트, 즉 한 군데에서만 사용하고 있다.
구글 크롬이 제공하는 기능이 lighthouse를 이용하여 treemap을 보니 한 군데에서만 쓰이면서 전체 자원의 2%나 사용하고 있다. 공간의 낭비라고 생각이 되어 이를 순수 자바스크립트 코드로 변경하기로 했다.

사용되는 jquery 함수는 세개가 나온다.

1. hide()

2. show()

3. fadeIn()

4. fadeOut()

 

살짝씩 뜯어보니 hide()는 display: none으로, show()는 display: '' 혹은 'block'으로 하고 있었다.

문제는 바로 fadeIn()과 fadeOut().

보이다시피 이건 애니메이션이 필요하다.

몇가지 고려사항이 있었다.

1. 이거 하나를 위해서 다른 css 파일을 만들고 싶지는 않다. 그리고 styled-component를 이참에 사용해보고 싶었기에 css 요소는 styled-component를 사용하였다.

2. 클래스 이름에 따라서 opacity, display 등등 css 속성을 다르게 주고 싶었다. 리액트는 선언형 프로그래밍 스타일을 추구하므로 state를 이용하여 .fade-in, .fade-out 이 두 가지 클래스를 입혔다.

3. board를 닫을 때에 messages를 빈배열([])로 만들어주려 하는데 opacity가 완전히 0이 될 때까지 기다려야 한다. 그리고 display는 none이 되어서는 안된다. jquery의 fadeIn/Out의 기본 duration은 400ms 라고 한다. 이를 이용하여 애니메이션이 완전히 종료되고 난 후에 messages를 빈배열로, display를 none으로 변경할 것이다.

4. 더 좋은 방법이 있겠지만 초반에 .hide()하는 방법은 마땅한것을 찾지 못했고 또다른 하나의 state를 만듦으로서 해당 컴포넌트의 연속적인 렌더링을 발생시키고 싶지는 않아서 useRef를 사용하였다. 더 좋은 방법이 있으면 업데이트로 하도록 하겠다.

 

하여 나온 결과물은 아래와 같다.

 

duration을 0.4로 상수화하였으며 css의 @keyframes 속성을 사용하여 중복된 코드를 줄이고 애니메이션을 만들었다.

초기 렌더링 시 useRef를 이용하여 board의 display를 none으로 하고 fadeIn() 함수 실행 시 board의 display를  block으로 설정하였다. 그리고 fadeOut()함수를 실행 한 뒤 지정된 duration * 1000 뒤에 messages를 []로 변경하였고 board의 display를 다시 none으로 변경하였다.

 그리고 boolean 타입의 state를 이용하여 class 이름을 변경하여 각 class에 맞는 애니메션을 입혔다.

/**
 * AlertBoardLayout 프로퍼티즈
 */
interface AlertBoardLayoutProps {
  id?: string;
  messages: AlertBoardMessagesProps[];
  setMessages?: (e: AlertBoardMessagesProps[]) => void;
  extraClass?: string;
  isNotCloseable?: boolean;
  withoutColDiv?: boolean;
}

const FADE_INOUT_DURATION = 0.4;

const BoardWrapper = styled.div`
  .fade-in {
    opacity: 1;
    transition: opacity ${FADE_INOUT_DURATION}s;
    -moz-animation: fadein ${FADE_INOUT_DURATION}s linear;
    -webkit-animation: fadein ${FADE_INOUT_DURATION}s linear;
    animation: fadein ${FADE_INOUT_DURATION}s linear;
  }

  .fade-out {
    opacity: 0;
    -moz-animation: fadeout ${FADE_INOUT_DURATION}s linear;
    -webkit-animation: fadeout ${FADE_INOUT_DURATION}s linear;
    animation: fadeout ${FADE_INOUT_DURATION}s linear;
  }

  @-webkit-keyframes fadein {
    0% {
      opacity: 0;
      transform: scale(0);
      transform-origin: top left;
    }
    100% {
      opacity: 1;
      transform: scale(1);
      transform-origin: top left;
    }
  }
  @keyframes fadein {
    0% {
      opacity: 0;
      transform: scale(0);
      transform-origin: top left;
    }
    100% {
      opacity: 1;
      transform: scale(1);
      transform-origin: top left;
    }
  }

  @-webkit-keyframes fadeout {
    0% {
      opacity: 1;
    }
    100% {
      opacity: 0;
    }
  }
  @keyframes fadeout {
    0% {
      opacity: 1;
    }
    100% {
      opacity: 0;
    }
  }
`;

/**
 * TODO:
 * 1. display: none, block 처리 제대로
 * 2. useRef보다 더 나은, 기존의 자원을 최대한 활용하며 렌더링을 더 이상 유발하지 않는 방법 찾기
 */
const Board: React.FC<AlertBoardLayoutProps> = ({ id, extraClass, isNotCloseable, messages, setMessages, withoutColDiv }) => {
  const [isBoardShow, setIsBoardShow] = useState<boolean>(false);
  const boardRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    if (boardRef.current) boardRef.current.style.display = 'none';
  }, []);

  const fadeIn = useCallback(() => {
    setIsBoardShow(true);
    if (boardRef.current) boardRef.current.style.display = 'block';
  }, []);

  const fadeOut = useCallback(() => {
    setIsBoardShow(false);
    setTimeout(() => {
      setMessages?.(defaultList);
      if (boardRef.current) boardRef.current.style.display = 'none';
    }, FADE_INOUT_DURATION * 1000);
  }, [setMessages]);

  useEffect(() => {
    messages && messages.length > 0 ? fadeIn() : fadeOut();
  }, [messages]);

  return (
    <BoardWrapper>
      <div id={id} ref={boardRef} className={`${isBoardShow ? 'fade-in' : 'fade-out'} alert alert-warning alert-dismissible ${extraClass ?? ''} ${withoutColDiv ? '' : 'pdr0'}`} role="alert">
        {isNotCloseable || (
          <button type="button" className="close" aria-label="Close" onClick={fadeOut}>
            <span aria-hidden="true">
              <i className="fal fa-times-circle"></i>
            </span>
          </button>
        )}
        <ul className="list-unstyled mgb0">
          {messages.map((e, index) => {
            return (
              <li key={`${e.labelName}${index}`} className={!withoutColDiv && e.labelColor ? e.labelColor : `${e.liClassName ? e.liClassName : ''}`}>
                {e.labelIconName && <i className={`fal ${e.labelIconName}`}></i>}
                <strong>{e.labelName ?? ' '}</strong>
                {e.value ?? ' '}
              </li>
            );
          })}
        </ul>
      </div>
    </BoardWrapper>
  );
};

 

 

Jquery fadeIn/Out 함수를 리액트 내에서 순수 자바스크립트로 변경함으로서 평소 제대로 실습해보고 싶었던 styled 컴포넌트를 사용해보았던 것이 큰 유익이라 할 수 있겠다. 결과물에는 styled-component에 props를 사용한 것이 없지만  messages && messages.length > 0 값에 따라서 display를 제어해보려 했고, 이를 BoardWrapper에 props로 넣어 사용해보기도 했다.(비록 실패했지만,,,) 이를 통해 styled-component에 대한 이해도를 더욱 높일 수 있었다.

 뿐만 아니라 나름 css 공부를 하기도 해서 좋았고! 한군데에서만 쓰이고 있는 jquery 모듈을 걷어낸다는 것이 뭔가 후련한 기분이다.