Vue 3의 새로운 기능은 무엇입니까?

게시 됨: 2022-03-10
빠른 요약 ↬ Vue 3에는 프레임워크를 사용하여 개발을 훨씬 쉽고 유지 관리할 수 있도록 하기 위한 여러 흥미로운 새 기능과 기존 기능의 변경 사항이 포함되어 있습니다. 이 기사에서는 이러한 새로운 기능 중 일부와 시작하는 방법을 살펴보겠습니다. 우리는 또한 기존 기능에 대한 몇 가지 변경 사항을 살펴볼 것입니다.

Vue 3의 출시와 함께 개발자는 읽기 쉽고 유지 관리 가능한 구성 요소를 구축하는 데 매우 유용한 몇 가지 새로운 기능과 Vue에서 애플리케이션을 구조화하는 개선된 방법을 제공하므로 Vue 2에서 업그레이드해야 합니다. 이 기사에서는 이러한 기능 중 일부를 살펴볼 것입니다.

이 튜토리얼이 끝나면 독자는 다음과 같이 할 것입니다.

  1. provide / inject 및 사용 방법을 알고 있습니다.
  2. Teleport 및 사용 방법에 대한 기본적인 이해가 있습니다.
  3. 프래그먼트에 대해 알고 사용하는 방법을 알아보세요.
  4. Global Vue API의 변경 사항에 대해 알아보세요.
  5. Events API의 변경 사항에 대해 알아보세요.

이 글은 Vue 2.x를 제대로 이해하고 있는 사람들을 대상으로 합니다. GitHub에서 이 예제에 사용된 모든 코드를 찾을 수 있습니다.

provide / inject

Vue 2.x에는 데이터(문자열, 배열, 개체 등)를 부모 구성 요소에서 자식 구성 요소로 직접 쉽게 전달할 수 있는 props 이 있었습니다. 그러나 개발 중에 부모 구성 요소에서 props 로 수행하기 더 어려운 깊게 중첩된 구성 요소로 데이터를 전달해야 하는 경우를 종종 발견했습니다. 이로 인해 Vuex Store, Event Hub가 사용되었으며 때로는 깊게 중첩된 구성 요소를 통해 데이터를 전달했습니다. 간단한 앱을 살펴보겠습니다.

Vue provide / inject 에는 일반 응용 프로그램 코드에서 사용하지 않는 것이 좋습니다.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { color: "", colors: ["red", "blue", "green"], }; }, }; </script>
 # childComponent.vue <template> <div class="hello"> <h1>{{ msg }}</h1> <color-selector :color="color"></color-selector> </div> </template> <script> import colorSelector from "@/components/colorComponent.vue"; export default { name: "HelloWorld", components: { colorSelector, }, props: { msg: String, color: String, }, }; </script> <!-- Add "scoped" attribute to limit CSS to this component only --> <style scoped> h3 { margin: 40px 0 0; } ul { list-style-type: none; padding: 0; } li { display: inline-block; margin: 0 10px; } a { color: #42b983; } </style>
 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { props: { color: String, }, }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

여기에 색상 목록이 포함된 드롭다운이 있는 방문 페이지가 있으며 선택한 colorchildComponent.vue 에 소품으로 전달합니다. 이 자식 구성 요소에는 템플릿 섹션에 표시할 텍스트를 허용하는 msg 소품도 있습니다. 마지막으로 이 구성 요소에는 이 구성 요소의 텍스트에 대한 클래스를 결정하는 데 사용되는 상위 구성 요소의 color 소품을 수락하는 하위 구성 요소( colorComponent.vue )가 있습니다. 이것은 모든 구성 요소를 통해 데이터를 전달하는 예입니다.

그러나 Vue 3에서는 새로운 제공 및 주입 쌍을 사용하여 더 깨끗하고 짧은 방법으로 이 작업을 수행할 수 있습니다. 이름에서 알 수 있듯이, 우리는 함수나 객체로 provide 을 사용하여 그러한 구성 요소가 얼마나 깊이 중첩되어 있는지에 관계없이 상위 구성 요소에서 중첩된 구성 요소로 데이터를 사용할 수 있도록 합니다. 다음과 같이 provide 하기 위해 하드 코딩된 값을 전달할 때 객체 형식을 사용합니다.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" :color="color" /> <select name="color" v-model="color"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { colors: ["red", "blue", "green"], }; }, provide: { color: 'blue' } }; </script>

그러나 provide 하기 위해 구성 요소 인스턴스 속성을 전달해야 하는 경우에는 기능 모드를 사용하므로 이것이 가능합니다.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "blue", colors: ["red", "blue", "green"], }; }, provide() { return { color: this.selectedColor, }; }, }; </script>

childComponent.vuecolorComponent.vue 모두에서 color props가 필요하지 않기 때문에 제거하겠습니다. provide 을 사용할 때 좋은 점은 상위 구성 요소가 제공하는 속성이 필요한 구성 요소를 알 필요가 없다는 것입니다.

이것을 필요로 하는 컴포넌트(이 경우 colorComponent.vue )에서 이것을 사용하려면 다음을 수행합니다.

 # colorComponent.vue <template> <p :class="[color]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

여기서 우리는 컴포넌트가 필요로 하는 필수 변수의 배열을 취하는 inject 을 사용합니다. 이 경우 color 속성만 필요하므로 전달합니다. 그 후에는 props를 사용할 때와 같은 방식으로 color 을 사용할 수 있습니다.

드롭다운을 사용하여 새 색상을 선택하려고 하면 colorComponent.vue 에서 색상이 업데이트되지 않으며 이는 기본적으로 provide 의 속성이 반응하지 않기 때문입니다. 이를 해결하기 위해 computed 된 방법을 사용합니다.

 # parentComponent.vue <template> <div class="home"> <img alt="Vue logo" src="../assets/logo.png" /> <HelloWorld msg="Vue 3 is liveeeee!" /> <select name="color" v-model="selectedColor"> <option value="" disabled selected> Select a color</option> <option :value="color" v-for="(color, index) in colors" :key="index">{{ color }}</option></select > </div> </template> <script> import HelloWorld from "@/components/HelloWorld.vue"; import { computed } from "vue"; export default { name: "Home", components: { HelloWorld, }, data() { return { selectedColor: "", todos: ["Feed a cat", "Buy tickets"], colors: ["red", "blue", "green"], }; }, provide() { return { color: computed(() => this.selectedColor), }; }, }; </script>

여기에서 사용자가 다른 색상을 선택할 때 반응하고 업데이트할 수 있도록 computed 된 색상을 가져오고 selectedColor 를 전달합니다. 계산된 메서드에 변수를 전달하면 value 이 있는 개체를 반환합니다. 이 속성은 변수 을 보유하므로 이 예에서는 colorComponent.vue 를 다음과 같이 업데이트해야 합니다.

 # colorComponent.vue <template> <p :class="[color.value]">This is an example of deeply nested props!</p> </template> <script> export default { inject: ["color"], }; </script> <style> .blue { color: blue; } .red { color: red; } .green { color: green; } </style>

여기에서는 computed 된 방법을 사용하여 color 을 반응형으로 만든 후 변경 사항을 나타내기 위해 colorcolor.value 로 변경합니다. 이 시점에서 이 구성 요소의 텍스트 class 는 부모 구성 요소에서 selectedColor 가 변경될 때마다 항상 변경됩니다.

점프 후 더! 아래에서 계속 읽기 ↓

텔레포트

앱이 사용하는 로직 때문에 구성 요소를 만들어 애플리케이션의 한 부분에 배치하지만 애플리케이션의 다른 부분에 표시되도록 의도된 경우가 있습니다. 이것의 일반적인 예는 전체 화면을 표시하고 덮기 위한 모달 또는 팝업입니다. 이러한 요소에 대한 CSS의 position 속성을 사용하여 이에 대한 해결 방법을 만들 수 있지만 Vue 3에서는 Teleport를 사용하여 수행할 수도 있습니다.

Teleport를 사용하면 기본 #app 컨테이너 Vue 앱이 래핑된 기본 #app 컨테이너에서 구성 요소를 문서의 원래 위치에서 가져와 사용 중인 페이지의 기존 요소로 이동할 수 있습니다. 좋은 예는 Teleport를 사용하여 헤더 구성 요소를 #app div 내부에서 header 로 이동하는 것입니다. Vue DOM 외부에 있는 요소로만 텔레포트할 수 있다는 점에 유의해야 합니다.

잘못된 요소로 텔레포트할 때 콘솔의 오류 메시지
콘솔의 텔레포트 오류 메시지: 터미널의 잘못된 텔레포트 대상 오류 메시지입니다. (큰 미리보기)

Teleport 구성 요소는 이 구성 요소의 동작을 결정하는 두 개의 props를 허용하며 다음과 같습니다.

  1. to
    이 소품은 클래스 이름, ID, 요소 또는 data-* 속성을 허용합니다. Teleport 요소를 동적으로 변경하고 반대로 :to to 을 전달하여 이 값을 동적으로 만들 수도 있습니다.
  2. :disabled
    이 소품은 Boolean 을 허용하며 요소 또는 구성요소의 텔레포트 기능을 토글하는 데 사용할 수 있습니다. 이것은 요소의 위치를 ​​동적으로 변경하는 데 유용할 수 있습니다.

Teleport를 사용하는 이상적인 예는 다음과 같습니다.

 # index.html** <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title> <%= htmlWebpackPlugin.options.title %> </title> </head> <!-- add container to teleport to --> <header class="header"></header> <body> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div></div> <!-- built files will be auto injected --> </body> </html>

Vue 앱의 기본 index.html 파일에서 헤더 구성 요소를 앱의 해당 지점으로 텔레포트하려고 하기 때문에 header 요소를 추가합니다. 또한 Teleport 구성 요소에서 스타일을 지정하고 쉽게 참조할 수 있도록 이 요소에 클래스를 추가했습니다.

 # Header.vue** <template> <teleport to="header"> <h1 class="logo">Vue 3 </h1> <nav> <router-link to="/">Home</router-link> </nav> </teleport> </template> <script> export default { name: "app-header", }; </script> <style> .header { display: flex; align-items: center; justify-content: center; } .logo { margin-right: 20px; } </style>

여기에서 헤더 구성 요소를 만들고 앱의 홈페이지에 대한 링크가 있는 로고를 추가합니다. 또한 Teleport 구성 요소를 추가하고 이 구성 요소가 이 요소 내부에서 렌더링되기를 원하기 때문에 to prop에 header 값을 제공합니다. 마지막으로 이 구성 요소를 앱으로 가져옵니다.

 # App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>

이 파일에서 헤더 구성 요소를 가져와 템플릿에 배치하여 앱에서 볼 수 있도록 합니다.

이제 앱의 요소를 검사하면 헤더 구성 요소가 header 요소 내부에 있음을 알 수 있습니다.

DevTools의 헤더 구성 요소
DevTools의 헤더 구성요소(큰 미리보기)

파편

Vue 2.x에서는 파일 template 에 여러 루트 요소를 포함할 수 없었고 해결 방법으로 개발자는 부모 요소의 모든 요소를 ​​래핑하기 시작했습니다. 이것이 심각한 문제처럼 보이지는 않지만 개발자가 이러한 요소를 감싸는 컨테이너 없이 구성 요소를 렌더링하고 싶지만 그렇게 해야 하는 경우가 있습니다.

Vue 3에서는 Fragments라는 새로운 기능이 도입되었으며 이 기능을 통해 개발자는 루트 템플릿 파일에 여러 요소를 가질 수 있습니다. 따라서 Vue 2.x에서는 입력 필드 컨테이너 구성 요소가 어떻게 생겼는지 알 수 있습니다.

 # inputComponent.vue <template> <div> <label :for="label">label</label> <input :type="type" : :name="label" /> </div> </template> <script> export default { name: "inputField", props: { label: { type: String, required: true, }, type: { type: String, required: true, }, }, }; </script> <style></style>

여기에 두 개의 props, labeltype 을 허용하는 간단한 양식 요소 구성 요소가 있으며 이 구성 요소의 템플릿 섹션은 div로 래핑됩니다. 이것은 반드시 문제는 아니지만 레이블과 입력 필드가 form 요소 내부에 직접 있기를 원하는 경우입니다. Vue 3를 사용하면 개발자는 이 구성 요소를 다음과 같이 쉽게 다시 작성할 수 있습니다.

 # inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>

단일 루트 노드를 사용하면 속성 은 항상 루트 노드에 귀속되며 Non-Prop Attributes 라고도 합니다. props 또는 emits 에 정의된 해당 속성이 없는 구성 요소에 전달된 이벤트 또는 속성입니다. 이러한 속성의 예로는 classid 가 있습니다. 그러나 다중 루트 노드 구성 요소의 요소 중 어떤 요소가 귀속되어야 하는지 명시적으로 정의해야 합니다.

이것은 위에서 inputComponent.vue 를 사용하는 것을 의미합니다.

  1. 상위 구성 요소의 이 구성 요소에 class 를 추가할 때 이 class 가 어떤 구성 요소에 속할 것인지 지정해야 합니다. 그렇지 않으면 속성이 적용되지 않습니다.
 <template> <div class="home"> <div> <input-component class="awesome__class" label="name" type="text" ></input-component> </div> </div> </template> <style> .awesome__class { border: 1px solid red; } </style>

속성을 지정해야 하는 위치를 정의하지 않고 이와 같은 작업을 수행하면 콘솔에 이 경고가 표시됩니다.

속성이 배포되지 않을 때 터미널의 오류 메시지
속성이 배포되지 않은 경우 터미널의 오류 메시지(큰 미리보기)

border 는 구성 요소에 영향을 주지 않습니다.

속성 분포가 없는 구성요소
속성 분포가 없는 구성요소(큰 미리보기)
  1. 이 문제를 해결하려면 이러한 속성을 배포하려는 요소에 v-bind="$attrs" 를 추가하세요.
 <template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>

여기에서 우리는 속성이 label 요소에 배포되기를 원한다고 awesome__class 에 말하고 있습니다. 이제 브라우저에서 요소를 검사하면 클래스가 이제 레이블에 추가되어 label 주위에 테두리가 있음을 알 수 있습니다.

속성 분포가 있는 구성요소
속성 분포가 있는 구성요소(큰 미리보기)

글로벌 API

Vue 애플리케이션의 main.js 파일에서 Vue.component 또는 Vue.use 를 보는 것은 드문 일이 아닙니다. 이러한 유형의 메소드는 글로벌 API로 알려져 있으며 Vue 2.x에는 상당히 많습니다. 이 방법의 문제 중 하나는 Vue에 모두 탑재되어 있기 때문에 다른 앱에 영향을 주지 않고 특정 기능을 앱의 하나의 인스턴스(앱에 둘 이상의 인스턴스가 있는 경우)로 분리하는 것이 불가능하다는 것입니다. 이것이 내가 의미하는 바입니다.

 Vue.directive('focus', { inserted: el => el.focus() }) Vue.mixin({ /* ... */ }) const app1 = new Vue({ el: '#app-1' }) const app2 = new Vue({ el: '#app-2' })

위 코드의 경우 Vue 지시문을 app1 과 연관시키고 Mixin을 app2 와 연관시키는 것이 불가능하지만 대신 두 앱에서 둘 다 사용할 수 있습니다.

Vue 3는 createApp 의 도입으로 이러한 유형의 문제를 해결하기 위한 시도로 새로운 Global API와 함께 제공됩니다. 이 메서드는 Vue 앱의 새 인스턴스를 반환합니다. 앱 인스턴스는 현재 전역 API의 하위 집합을 노출합니다. 이를 통해 Vue 2.x에서 Vue 를 변경하는 모든 API(구성 요소, mixin, 지시문, 사용 등)가 이제 개별 앱 인스턴스로 이동되고 이제 Vue 앱의 각 인스턴스에 고유한 기능이 있을 수 있습니다. 다른 기존 앱에 영향을 주지 않고

이제 위의 코드를 다음과 같이 다시 작성할 수 있습니다.

 const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })

그러나 모든 앱에서 공유하려는 기능을 생성하는 것이 가능하며 이는 팩토리 기능을 사용하여 수행할 수 있습니다.

이벤트 API

개발자가 Vuex Store를 사용하는 것 외에 부모 대 자식 관계가 없는 구성 요소 간에 데이터를 전달하기 위해 채택한 가장 일반적인 방법 중 하나는 이벤트 버스를 사용하는 것입니다. 이 방법이 일반적인 이유 중 하나는 시작하기가 쉽기 때문입니다.

 # eventBus.js const eventBus = new Vue() export default eventBus;

그 다음에는 이 파일을 main.js 로 가져와 앱에서 전역적으로 사용할 수 있도록 하거나 필요한 파일로 가져오는 것입니다.

 # main.js import eventBus from 'eventBus' Vue.prototype.$eventBus = eventBus

이제 다음과 같이 이벤트를 내보내고 발생된 이벤트를 수신할 수 있습니다.

 this.$eventBus.$on('say-hello', alertMe) this.$eventBus.$emit('pass-message', 'Event Bus says Hi')

이와 같은 코드로 채워진 Vue 코드베이스가 많이 있습니다. 그러나 Vue 3에서는 $on , $off$once 가 모두 제거되었지만 $emit 은 자식 구성 요소가 부모 구성 요소에 이벤트를 내보내는 데 필요하기 때문에 여전히 사용할 수 있기 때문에 불가능합니다. 이에 대한 대안은 provide / inject 또는 권장되는 타사 라이브러리를 사용하는 것입니다.

결론

이 기사에서는 provide / inject 쌍을 사용하여 상위 구성 요소에서 깊이 중첩된 하위 구성 요소로 데이터를 전달하는 방법을 다루었습니다. 또한 앱의 한 지점에서 다른 지점으로 구성 요소를 재배치하고 전송할 수 있는 방법도 살펴보았습니다. 우리가 살펴본 또 다른 사항은 다중 루트 노드 구성 요소와 속성이 제대로 작동하도록 배포하는 방법입니다. 마지막으로 Events API 및 Global API에 대한 변경 사항도 다루었습니다.

추가 리소스

  • "ES6+에서 JavaScript 팩토리 기능", Eric Elliott, Medium
  • "이벤트 버스를 사용하여 Vue 구성 요소 간에 소품 공유", Kingsley Silas, CSS-Tricks
  • 동일한 대상에서 다중 텔레포트 사용, Vue.js 문서
  • 비 소품 속성, Vue.js 문서
  • 반응성으로 작업하기, Vue.js 문서
  • teleport , Vue.js 문서
  • 프래그먼트, Vue.js 문서
  • 2.x 구문, Vue.js 문서