[React] 리렌더링 시 html element에 애니메이션 적용하기

이동현·2022년 7월 10일
3

React

목록 보기
15/16

상황

사용자 입력폼을 만드는 작업에서 어떤 입력 input 태그에 사용자가 focus를 하면 좌측에 해당 질문의 제목이 애니메이션 효과와 함께 나타나는 UI를 디자인했고 적용하려고 했다.

위 그림과 같이 사용자가 현재 입력하고 있는 input 의 제목을 좌측 화면에 크게 표시하는데 이 때 우측의 사용자 입력폼의 focus가 달라질 때 좌측의 제목이 animation 효과를 매번 주고 싶었다.

이를 위해서 currentQuestion 이라는 컴포넌트 내에서 관리하는 state를 정의하고 focus가 바뀔때마다 해당 Input의 제목으로 상태를 업데이트 해주는 식으로 좌측의 제목을 변경해주고 싶었다.

아래 코드에 위 설명한 내용이 코드로 구현돼있다.

SubmitReviewPage.tsx

function SubmitReviewPage() {
  //좌측에 애니메이션과 함께 크게 출력되는 현재 질문의 제목 state
  const [currentQuestion, setCurrentQuestion] = useState<Question>(dummyData.questions[0]);
  ...
  return (
    <>
      <div className={cn(styles.container)}>
        <Logo />

        // 좌측에 큰 폰트로 애니메이션 효과와 함께 보여질 질문의 제목
        <Text
          className={cn(styles.title)}
          size={40}
          weight="bold"
        >
          {currentQuestion.questionValue}
        </Text>
        ...
      <div className={cn(styles.container)}>
        <form onSubmit={onSubmitReviewForm}>
          ...
          {questions.map((question, index) => (
            //사용자가 입력할 input
            <div className={cn(styles.fieldSetContainer)} key={question.questionId}>
              <FieldSet
                size="large"
                title={question.questionValue}
                description={question.questionDescription}
              >
                <TextBox
                  value={questions[index].answerValue}
                  onFocus={() => onUpdateCurrentQuestion(index)}
                  onChange={(e) => onUpdateAnswer(e.target.value, index)}
                />
              </FieldSet>
            </div>
          ))}
          ...
      </div>
    </>
  );
}

styles.module.scss

.title {
  width: 80%;
  margin-top: 1.75rem;
  letter-spacing: 0.25rem;
  line-height: 3.5rem;
  word-break: keep-all;

  @include animate(0.5s ease) {
    from {
      opacity: 0;
      filter: blur(1rem);
      transform: scale(1.2) translateX(-50%);
    }

    to {
      opacity: 1;
      filter: blur(0rem);
      transform: scale(1) translateY(0%);
    }
  }
}

문제

그런데 맨 처음에 화면이 렌더링 될 때는 제목에 애니메이션 효과가 적용이 되었다. 그런데 우측 사용자 입력폼에서 사용자가 다른 질문폼에 focus를 줬을 때 좌측의 currentQuestion으로 관리되는 상태가 업데이트되면서 제목이 리렌더링 되면서 바뀌기는 하지만 애니메이션 효과가 적용되지 않는 버그가 있었다.

이런 버그가 발생하는 이유는 리액트가 재조정과정을 통해서 Virtual DOM을 업데이트할 때 태그 자체가 아니라 같은 태그 안에 Text만 바뀌었다고 인지하고 업데이트를 하기 때문에 태그 자체가 리렌더링 되지 않는 것이다. 그러면 당연히 애니메이션 효과는 태그에 지정돼있기 때문에 발생하지 않는 것이다.

해결

리액트의 재조정과정에서 개발자가 직접 코드레벨에서 리액트에게 재조정시 리렌더링 할 수 있도록 하는 장치가 있는데 그것이 바로 key 속성이다.

리액트는 key속성을 재조정 과정에서 참고한다. 키 값이 바뀌면 해당 태그가 완전히 바뀌었다고 리액트는 판단하고 태그 자체를 Virtual DOM tree에서 제거하고 다시 생성할 것이다. 그렇게 되면 태그가 다시 렌더링 되기 때문에 애니메이션 효과가 적용이 된다.

위 코드 스니펫을 아래 코드와 같이 수정하면 의도한대로 입력폼의 focus가 이동할 떄 좌측의 큰 질문 제목에 애니메이션 효과가 적용되면서 업데이트가 된다.

SubmitReviewPage.tsx

function SubmitReviewPage() {
  //좌측에 애니메이션과 함께 크게 출력되는 현재 질문의 제목 state
  const [currentQuestion, setCurrentQuestion] = useState<Question>(dummyData.questions[0]);
  ...
  return (
    <>
      <div className={cn(styles.container)}>
        <Logo />

        // 좌측에 큰 폰트로 애니메이션 효과와 함께 보여질 질문의 제목
        <Text
          key={currentQuestion.questionValue}
          //key값을 업데이트해주는 state로 넣어주면 key값이 바뀔때마다
          //React는 재조정과정에서 해당 컴포넌트(<Text />)를 다시 렌더링한다
          className={cn(styles.title)}
          size={40}
          weight="bold"
        >
          {currentQuestion.questionValue}
        </Text>
        ...
      <div className={cn(styles.container)}>
        <form onSubmit={onSubmitReviewForm}>
          ...
          {questions.map((question, index) => (
            //사용자가 입력할 input
            <div className={cn(styles.fieldSetContainer)} key={question.questionId}>
              <FieldSet
                size="large"
                title={question.questionValue}
                description={question.questionDescription}
              >
                <TextBox
                  value={questions[index].answerValue}
                  onFocus={() => onUpdateCurrentQuestion(index)}
                  onChange={(e) => onUpdateAnswer(e.target.value, index)}
                />
              </FieldSet>
            </div>
          ))}
          ...
      </div>
    </>
  );
}

참조

stack overflew

profile
Dom Hardy : 멋쟁이 개발자 되기 인생 프로젝트 진행중

0개의 댓글