cover

TransitionGroup은 list와 같이 여러 개의 태그가 들어가는 경우를 고려한 Transition 컴포넌트입니다. 주로 v-for와 같이 사용합니다. 어떻게 적용되는지 알아볼까요?


TransitionGroup

Transition 처럼 동일한 속성을 가지고 있고, class도 동일하게 나타나고 Javascript hook도 가지고 있습니다. 다만 다음과 같이 몇 가지 차이만 있을 뿐입니다.

감싸는 태그가 없이 v-for 등을 이용해 여러 개 태그가 들어가야합니다. 대신 tag 라는 속성을 통해 감쌀(wrap) 태그를 지정할 수 있습니다.
리스트의 태그들은 각자 나타나거나 사라지고 순서만 바뀌기 때문에, 서로 영향을 주지 않습니다. 따라서 transition mode(out-in, in-out)가 없습니다.
각 태그들은 반드시 고유한 key 속성 값을 가지고 있어야한다.
v-xxx와 같은 클래스는 그걸 감싸는 컨테이너(container)가 아닌 태그 각각에 들어간다.
info
      만약 컴포넌트의 template 속성을 사용한다면 <TransitionGroup>이 아닌 <transition-group>으로 사용해야 합니다.
    

위 차이를 좀 더 자세히 이해하기 위해 예시들을 살펴보겠습니다.

Enter / Leave Transitions

보통 TransitionGroup은 v-for 디렉티브와 같이 사용됩니다.

html
      <script setup>
import { ref } from "vue";

const items = ref([1, 2, 3]);
</script>

<template>
  <button @click="items.push(0)">Add</button>
  <button @click="items.pop()">Remove</button>

  <TransitionGroup>
    <li v-for="item in items" :key="item">
      {{ item }}
    </li>
  </TransitionGroup>
</template>
    
info
      위에서 언급했듯이 반드시 key값으로 각 태그가 구분될 수 있게 해줘야합니다. 예를 들어 맨 끝에 push하는 게 아니라 중간에 랜덤한 위치에 넣는다면 enter 애니메이션이 동작하지 않는 문제가 발생합니다.
    

여기에 간단한 transition을 넣어보겠습니다.

css
      .v-enter-active,
.v-leave-active {
  transition: all 0.25s ease-out;
}

.v-enter-from {
  opacity: 0;
  transform: translateY(30px);
}

.v-leave-to {
  opacity: 0;
  transform: translateY(-30px);
}
    
loading

잘 적용된 것을 볼 수 있습니다.

tag 속성 적용하기

렌더링된 HTML 코드를 보면 약간 아쉬운 것이 있습니다. 바로 ul 태그로 감싸지지 않았다는 것인데요.

loading

tag 속성을 통해 감쌀 태그를 지정할 수 있습니다. 그리고 해당 태그에 원하는 속성을 넣고 싶다면 TransitionGroup에 넣으면 됩니다.

html
      <TransitionGroup tag="ul" class="test">
	...
</TransitionGroup>
    
loading

Move Transitions

위 예제는 각 태그가 사라지거나 없어지는 정도이지만 순서를 바꾸는 경우는 어떨까요? 한 칸씩 shift되게 해보겠습니다.

html
      <template>
  <button @click="items.push(items.shift())">Shift</button>
  <TransitionGroup tag="ul">
    <li v-for="item in items" :key="item">{{ item }}</li>
  </TransitionGroup>
</template>
    
loading

애니메이션이 동작하지 않습니다. 이건 나타나거나 사라지는 것이 아니라 움직였을 뿐이기 때문인데요. 그래서 별개의 클래스가 하나 더 존재합니다. 바로 v-move입니다.

html
      .v-move,
.v-enter-active,
.v-leave-active {
  transition: all 0.25s ease-out;
}
    
loading

이제 잘되네요! enter와 leave와 달리 from이나 to 같은 클래스가 없으니 v-move class만으로 적절히 구현해야합니다.

Hook에 데이터 전달하기

TransitionGroup도 당연히 hook이 존재합니다. 그런데 만약 각 아이템의 순서에 따라 애니메이션의 딜레이를 다르게 주고 싶다고 해보겠습니다. CSS의 경우 nth-child를 통해 animation-delay를 적용하면 되는데 javascript의 경우 그럴 수 없습니다. 따라서 dataset을 적용해주면 됩니다.

html
      <TransitionGroup
	@enter="onEnter"
>
  <li
    v-for="(item, index) in computedList"
    :key="item.msg"
    :data-index="index"
  >
    {{ item.msg }}
  </li>
</TransitionGroup>
    

data-index로 index값을 전달합니다. 그러면 onEnter 에서 태그가 가진 dataset 값으로 각각 다른 방식으로 적용할 수 있습니다.

html
      function onEnter(el, done) {
	// el.dataset.index 를 이용해 다르게 딜레이 적용
}
    

공식 홈페이지에 좋은 예제가 있으니 참조 바랍니다.