※ 일본의 한 블로그 글을 번역한 포스트입니다. 오역 및 의역, 직역이 있을 수 있으며 틀린 내용이 있다면 지적해주시면 감사하겠습니다.
꽤 이전에 Vue2와 Vue3의 차이점에 대해서 다룬적이 있는데, 조금 더 상세하게 다룬 글을 발견하여 공유하고자 한다.
대상 독자
- Vue2를 Vue3로 버전업을 하는 중 혹은 버전업을 검토하고 있는 사람
- Vue를 다루지만, 교양으로 Vue3를 캐치업하고 싶은 사람
- Vue2의 기능, 주의점을 보통 프론트엔드를 만지지 않는 멤버에게 설명하고 싶은 사람
새롭게 서포트된 API와 구문
CSS v-bind
CSS의 속성에 대해서 리액티브한 값의 바인딩이 가능하게 됐다. 내부적으로는 CSS 변수의 값을 리액티브 변경하는 것으로 값을 바꿔준다.
참고로 Vue2.7버전에서도 이용할 수 있다.
<template>
<div class="text">hello</div>
</template>
<script setup>
import { ref } from "vue";
const color = ref("red"); // bind대상
</script>
<style>
.text {
color: v-bind(color); /* 이 값이 리액티브해진다 */
}
</style>
Teleport
<Teleport>는 컴포넌트인 템플릿의 일부를 그 컴포넌트의 DOM 계층의 바깥쪽에 존재하는 DOM 노드로 텔레포드하는 것이다.
Vue2의 portal-vue와 동일한 기능이다. 새롭게 스택킹 컨텍스트를 생성하는 것으로 z-index에서의 중첩 제어를 회필할 수 있다.
<template>
<div class="app">
<Teleport to="body">
<Modal>teleport</Modal>
</Teleport>
</div>
</template>
Fragments
Fragments를 사용하는 것으로 Vue3에서는 <template> 바로 아래에 동일한 계층으로 여러 개의 요소를 배치할 수 있게 된다. 불필요한 wrapper의 div를 만들지 않아도 된다.
그러나 fragment를 사용하고 있는 경우, 부모 요소에서 부터 class 등의 속성을 추가할 때에 자식 요소쪽 명시적으로 v-bin="$attrs"를 설정하지 않아도 속성이 부여되지 않으므로 주의해야한다. 더욱이 scoped css의 경우 :deep()도 필요하다.
<template>
<div>...</div>
<div v-bind="$attrs">...</div>
<!-- 이 요소에 부모쪽에서 지정한 속성이 부여된다. -->
<div>...</div>
</template>
Suspense (Experimental)
<Suspense>는 컴포넌트 트리의 비동기 의존관계를 제어하기 위한 구성 컴포넌트이다. 컴포넌트 트리의 아래에 있는 여러 개의 들여스기 된 비동기 의존관계가 해결될 때를 기다릴 동안 로딩 상태를 렌더링할 수 있다.
비동기 컴포넌트를 사용할 때에 유효하지만, Experimental한 기능으로 API가 바뀔 가능성도 있으므로 사용은 피하는 편이 좋을지도 모른다.
<Suspense>
<!-- 들여쓰기된 비동기 의존관계를 가진 컴포넌트 -->
<Dashboard>
<!-- #fallback 로 로딩상태를 표시 -->
<template fallback>
Loading...
</template>
</Suspense>
script setup 구문
하나의 파일 컴포넌트(SFC)내에서 Composition API를 사용할 때의 신택스 슈가이다. 다음과 같은 장점이 있다.
- 보다 간결한 코드를 쓸 수 있다.
- 단순한 TypeScript를 사용하여 속성과 발행된 이벤트를 선언하는 기능이다.
- 실행시의 퍼포먼스가 향상된다.
- IDE 형 추론 퍼포먼스가 향상된다.
참고로 Vue2.7에서도 이용가능하다.
<template>
<ChildA />
{{ text }}
<button @click="click">{{ msg }}</button>
<ChildB />
<ChildC />
</template>
<script setup lang="ts">
// 자식 컴포넌트의 정의
import ChildA from "./ChildA.vue";
import ChildB from "./ChildB.vue";
import ChildC from "./ChildC.vue";
// reactive의 정의
const text = ref("");
// props의 정의
const props = withDefaults(defineProps<{ msg?: string }>(), {
msg: "hello",
});
// emit의 정의
const emit = defineEmits<{
(e: "change", msg: string): void;
}>();
const click = () => {
emit("change", "ok");
};
</script>
Reactive API의 추가
리액티브관련 API가 다수 추가됐다. 리액티브의 제어로 곤란해지면 써보는 것이 좋을지도 모른다. 일부 나열해보자면, 다음과 같다.
- shallowRef() : ref()의 얕은 버전
- customRef() : 의존관계의 추적과 변경의 트리거를 명시적으로 제어하여 커스터마이즈된 ref를 작성
- toRaw() : Vue에서 작성된 프록시의 원본 개체를 반환
- v-memo : 템플릿의 서브 트리의 메모화
주의해야할 Breaking Changes는?
Vue3에서는 v-model의 사양이 꽤 변경됐다.
- v-model을 받아들이는 커스텀 컴포넌트의 props, event 명이 변경
- prop: value > modelValue
- event : input > update:modelValue
- v-bind.sync의 폐지
- 1개 이상의 v-model 정의가 가능해짐
- v-model의 modifier가 증가
배열 요소의 변경이 표준에서 Watch되지 않는다.
배열의 내부 요소의 변경을 watch로 검지하도록 한다고해도 검지되지 않게 됐다. 배열의 요소 변경을 검지하고 싶을 때는 deep 옵션을 붙일 필요가 있게 됐다.
watch(
reactiveVal,
(newVal, oldVal) => {
console.log(`${oldVal} -> ${newVal}`);
},
{
deep: true, // 배열 요소의 변경에는 이것이 필요
}
);
Array, Object의 변경으로 Vue.set, Vue.remove 사용이 불가능해졌다.
Vue2에서는 배열이나 배오브젝트의 키 지정의 바꿔쓰기/삭제가 리액티브가 아니라는 주의점이 있었지만 Vue3에서는 해소되어 Vue.set이나 Vue,remove가 불필요해졌다.
deep 셀렉터의 작성법이 변경됐다.
scoped 환경에서 자식 컴포넌트의 요소에 대해 스타일을 적용하고 싶은 경우에 사용하는 셀렉터의 지정 방법이 변경됐다. :deep()으로 지정해야 할 필요가 생겼다.
<style scoped>
/* 자식 컴포넌트의 `.b`에 적용할 수 있다 */
.a :deep(.b) {
/* ... */
}
</style>
그 외에도 다양한 변경점이 있으므로 마이그레이션 가이드를 확인하는 것이 좋다.
참고자료
'IT > WEB' 카테고리의 다른 글
[Vue3] Vue3의 Composition API (0) | 2023.09.03 |
---|---|
[CSS] Scoped CSS에 있어서의 CSS 설계 방법 (0) | 2023.08.21 |
Atomic Design을 그만두고 디렉토리 구조를 바꾼 이야기 (0) | 2023.07.30 |
[Spring] @Autowired (0) | 2023.07.09 |
클린 아키텍처2 (0) | 2023.02.15 |