성능 향상
강력한 타입스크립트 연계
Inject 와 Provide
props 를 개선한 버전
기존 props 사용 방식 (aka. prop 드릴링)
props 개선 방식 (aka. Inject & Provide)
provide 와 inject 사용 방법
/*
provide 사용법
- <script setup> 이 아니라면, setup 함수에서 사용
- 앱 수준 제공 가능 : 앱에서 렌더링되는 모든 컴포넌트에서 사용
*/
<script setup>
import { ref, provide } from 'vue'
import { fooSymbol } from './injectionSymbols'
// 키 : 자식 컴포넌트에서 값을 조회할 때 사용
// 값 : 제공되는 값으로 모든 타이 가능함
provide(/* 키 */ 'message', /* 값 */ '안녕!')
// key 중복방지를 위해 symbol 을 사용하여 key 생성 후 사용 가
provide(fooSymbol, count)
// 반응성 값을 주입시켰기 때문에 자식 컴포넌트에서도 hello 데이터를 변경할 수 있음
const refData = ref(0);
provide('hello', refData);
</script>
/*
inject 사용법
- <script setup> 이 아니라면, setup 함수에서 동기적으로 호출이 필요함
*/
<script setup>
import { inject } from 'vue'
import { fooSymbol } from './injectionSymbols'
// message 로 주입된 데이터를 받음
const message = inject('message')
// 두번째 인자는 foo 키로 주입된 데이터가 없을 때 사용하는 기본
const bar = inject('foo', 'default value')
// fooSymbol 로 주입된 데이터를 받음
const foo2 = inject(fooSymbol)
// num 데이터가 없을 경우, 동작할 함수를 정의
// 세번째 매개변수는 기본값이 팩토리함수로 처리되어야 하는지 나타냄
const num = inject('num', () => { return Math.random() }, true);
</script>
빌트인 컴포넌트
vue 2 → vue 3 업그레이드할 때 수정할 부분
transition 과 연관된 prop 명칭을 변경한다
ex) v-enter → v-enter-from 로 변경
// vue 2
.v-enter,
.v-leave-to {
opacity: 0;
}
.v-leave,
.v-enter-to {
opacity: 1;
}
// vue 3
.v-enter-from,
.v-leave-to {
opacity: 0;
}
.v-leave-from,
.v-enter-to {
opacity: 1;
}
차이점
| transition | transitionGroup |
| --- | --- |
| key 속성 x | key 속성을 필수로 가져야 함 |
| 엘리먼트에 적용됨 | 목록 개별에 transition 이 적용됨 |
// css 애니메이션 사용할 경우
<Transition name="bounce">
<p v-if="show" style="text-align: center;">
안녕! 여기에 탄력적인 텍스트가 있어요!
</p>
</Transition>
.bounce-enter-active {
animation: bounce-in 0.5s;
}
.bounce-leave-active {
animation: bounce-in 0.5s reverse;
}
// JS 에서 트랜지션 처리하는 경우
<Transition
@before-enter="onBeforeEnter"
@enter="onEnter"
@after-enter="onAfterEnter"
@enter-cancelled="onEnterCancelled"
@before-leave="onBeforeLeave"
@leave="onLeave"
@after-leave="onAfterLeave"
@leave-cancelled="onLeaveCancelled"
>
<!-- ... -->
</Transition>
// 엘리먼트가 DOM에 삽입되기 전에 호출됩니다.
// 이것을 사용하여 엘리먼트의 "enter-from" 상태를 설정합니다.
function onBeforeEnter(el) {}
// 엘리먼트가 삽입되고 1 프레임 후 호출됩니다.
// 진입 애니메이션을 시작하는 데 사용합니다.
function onEnter(el, done) {
// CSS와 함께 사용되는 경우, 선택적으로
// 트랜지션 종료를 나타내기 위해 done 콜백을 호출합니다.
done()
}
// 진입 트랜지션이 완료되면 호출됩니다.
function onAfterEnter(el) {}
// 진입 트랜지션이 완료되기 전, 취소될 때 호출됩니다.
function onEnterCancelled(el) {}
// 진출 훅 전에 호출됩니다.
// 대부분의 경우 그냥 진출 훅을 사용해야 합니다.
function onBeforeLeave(el) {}
// 진출 트랜지션이 시작될 때 호출됩니다.
// 진출 애니메이션을 시작하는 데 사용합니다.
function onLeave(el, done) {
// CSS와 함께 사용되는 경우, 선택적으로
// 트랜지션 종료를 나타내기 위해 done 콜백을 호출합니다.
done()
}
// 진출 트랜지션이 완료되고,
// 엘리먼트가 DOM에서 제거된 후 호출됩니다.
function onAfterLeave(el) {}
// v-show 트랜지션에서만 사용 가능합니다.
function onLeaveCancelled(el) {}
Composition API
더 나은 로직 재사용
더 유연한 코드 구성
더 나은 유형 추론
더 작은 번들과 더 적은 오버헤드
** 연관된 로직은 하나로 묶어 생산성 높게 사용하기 위함 **
// src/components/UserRepositories.vue
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
data () {
return {
repositories: [], // 1
filters: { ... }, // 3
searchQuery: '' // 2
}
},
computed: {
filteredRepositories () { ... }, // 3
repositoriesMatchingSearchQuery () { ... }, // 2
},
watch: {
user: 'getUserRepositories' // 1
},
methods: {
getUserRepositories () {
// `this.user`를 사용해서 유저 레포지토리 가져오기
}, // 2
updateFilters () { ... }, // 3
},
mounted () {
this.getUserRepositories() // 1
}
}
// src/components/UserRepositories.vue
import { toRefs } from 'vue'
import useUserRepositories from '@/composables/useUserRepositories'
import useRepositoryNameSearch from '@/composables/useRepositoryNameSearch'
import useRepositoryFilters from '@/composables/useRepositoryFilters'
export default {
components: { RepositoriesFilters, RepositoriesSortBy, RepositoriesList },
props: {
user: { type: String }
},
/**
props 가 resolved 될 때 실행되는 함수
beforeCreate , created 는 없음
**/
setup(props) {
// 반응성 변수로 만들기
// toRefs 를 사용했기 때문에 value 로 접근
const { user } = toRefs(props)
const { repositories, getUserRepositories } = useUserRepositories(user)
const {
searchQuery,
repositoriesMatchingSearchQuery
} = useRepositoryNameSearch(repositories)
const {
filters,
updateFilters,
filteredRepositories
} = useRepositoryFilters(repositoriesMatchingSearchQuery)
return {
repositories: filteredRepositories, // repositories 에 필터링된 데이터를 넣기 위함
getUserRepositories,
searchQuery,
filters,
updateFilters
}
}
}
// src/composables/useUserRepositories.js
import { fetchUserRepositories } from '@/api/repositories'
import { ref, onMounted, watch, toRefs } from 'vue'
// composable 예시 1
export default function useUserRepositories(user) {
const repositories = ref([])
const getUserRepositories = async () => {
repositories.value = await fetchUserRepositories(user.value)
}
// mounted 에서
onMounted(getUserRepositories)
/**
watch 가능 파라미터
1. 감시를 원하는 참조변수 or function
2. 콜백함수
3. watchOptions (deep, immediate)
**/
watch(user, getUserRepositories)
return {
repositories,
getUserRepositories
}
}
// src/composables/useRepositoryNameSearch.js
import { ref, onMounted, watch, toRefs } from 'vue'
// composable 예시 1
export default function useRepositoryNameSearch(repositories) {
const searchQuery = ref('')
const repositoriesMatchingSearchQuery = computed(() => {
return repositories.value.filter(repository => {
return repository.name.includes(searchQuery.value)
})
})
return {
searchQuery,
repositoriesMatchingSearchQuery
}
}
reactive() | 하위 중첩 속성까지 전파되는 깊은 반응성으로, 깊은 반응성이 싫다면 shallowReative() 를 사용하면 된다. |
---|---|
객체 타입에서만 사용 가능 | |
사용 예시 |
setup() 에서 호출되어야 하고, return 데이터 또한 setup() 에서 반환 되어야 한다.
import { useMouse } from './mouse.js'
import { useFetch } from './fetch.js'
export default {
setup() {
const { data, error } = useFetch('...')
return { x, y, data, error }
},
mounted() {
// setup()에서 노출된 속성은 `this`에서 접근할 수 있습니다.
console.log(this.x)
}
}
// 주로 쓰는 방법
// 모든 컴포넌트에서 사용한다면 이 패턴을 반복해야하지만, 이 부분을 composable 함수로 뺀다면 재사용이 가능하다.
<script setup>
import { ref } from 'vue'
const data = ref(null)
const error = ref(null)
fetch('...')
.then((res) => res.json())
.then((json) => (data.value = json))
.catch((err) => (error.value = err))
</script>
<template>
<div v-if="error">앗! 에러 발생: {{ error.message }}</div>
<div v-else-if="data">
로드된 데이터:
<pre>{{ data }}</pre>
</div>
<div v-else>로딩...</div>
</template>
// useFetch.js
import { ref, isRef, unref, watchEffect } from 'vue'
export function useFetch(url) {
const data = ref(null)
const error = ref(null)
async function doFetch() {
// 가져오기 전에 상태 재설정..
data.value = null
error.value = null
// watchEffect()에 의해 종속성으로 추적되도록
// URL 값을 동기식으로 resolve합니다.
const urlValue = unref(url)
try {
// 인위적인 딜레이 / 무작위 애러
await timeout()
// unref()는 ref이면 ref 값을 반환합니다.
// 그렇지 않으면 값을 있는 그대로 반환합니다.
const res = await fetch(urlValue)
data.value = await res.json()
} catch (e) {
error.value = e
}
}
if (isRef(url)) {
// 설정하기: 입력 URL이 ref인 경우 반응적 다시 가져오기
watchEffect(doFetch)
} else {
// 그렇지 않으면 한 번만 가져오기
doFetch()
}
return { data, error, retry: doFetch }
}
<script setup>
import { ref, computed } from 'vue'
import { useFetch } from './useFetch.js'
const baseUrl = 'https://jsonplaceholder.typicode.com/todos/'
const id = ref('1')
const url = computed(() => baseUrl + id.value)
const { data, error, retry } = useFetch(url)
</script>
<template>
Load post id:
<button v-for="i in 5" @click="id = i">{{ i }}</button>
<div v-if="error">
<p>앗! 오류 발생: {{ error.message }}</p>
<button @click="retry">재시도</button>
</div>
<div v-else-if="data">로드된 데이터: <pre>{{ data }}</pre></div>
<div v-else>로딩...</div>
</template>
<template>
<**MyComponent** />
</template>
**// 동적 컴포넌트**
<script setup>
import Foo from './Foo.vue'
import Bar from './Bar.vue'
</script>
<template>
<component :is="Foo" />
<component :is="someCondition ? Foo : Bar" />
</template>
**// 재귀 컴포넌트**
import { FooBar as FooBarChild } from './components'
<template>
<FooBar />
</template>
**// 네임스페이스 컴포넌트**
// 단일 파일에서 여러 컴포넌트를 가져올 때 사용
<script setup>
import * as Form from './form-components'
</script>
<template>
<Form.Input>
<Form.Label>레이블</Form.Label>
</Form.Input>
</template>
```
vue-cli와 Vue 버전 업그레이드 후 @vue/compat 설치
1. vue-cli 환경
@vue/cli-service 업그레이드
2. webpack 환경
vue-loader 를 ^16.0.0 로 업그레이드
"dependency": {
compat 모드 호환
// vue.config.js
module.exports = {
chainWebpack: config => {
config.resolve.alias.set('vue', '@vue/compat')
config.module
.rule('vue')
.use('vue-loader')
.tap(options => {
return {
...options,
compilerOptions: {
compatConfig: {
MODE: 2
}
}
}
})
}
컴파일 에러 고치기
Vue 3 주요 변경사항에 따라 코드 수정하기
주요 패키지(vuex, vue-router 등) 버전 업그레이드 하기
그 외 라이브러리들 버전 맞추거나 교체하기
@vue/compat 제거하기