Vue 3の新機能
公開: 2022-03-10Vue 3のリリースでは、開発者はVue 2からアップグレードする必要があります。これは、読みやすく保守しやすいコンポーネントの構築に非常に役立ついくつかの新機能と、Vueでアプリケーションを構造化するための改善された方法が付属しているためです。 この記事では、これらの機能のいくつかを見ていきます。
このチュートリアルの最後に、読者は次のことを行います。
-
provide / inject
とその使用方法について知ってください。 - テレポートとその使用方法の基本を理解している。
- フラグメントとその使用方法について知ってください。
- GlobalVueAPIに加えられた変更について知ってください。
- イベントAPIに加えられた変更について知ってください。
この記事は、Vue2.xを正しく理解している人を対象としています。 この例で使用されているすべてのコードはGitHubにあります。
provide / inject
Vue 2.xには、親コンポーネントから子コンポーネントに直接データ(文字列、配列、オブジェクトなど)を簡単に渡すことができるprops
がありました。 しかし、開発中に、親コンポーネントから深くネストされたコンポーネントにデータを渡す必要がある場合がよくありました。これは、 props
では行うのがより困難でした。 その結果、Vuex Store、Event Hubが使用され、場合によっては、深くネストされたコンポーネントを介してデータが渡されました。 簡単なアプリを見てみましょう。
Vue 2.2.0には、一般的なアプリケーションコードでの使用が推奨されていない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>
ここでは、色のリストを含むドロップダウンのあるランディングページがあり、選択したcolor
を小道具としてchildComponent.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.vue
とcolorComponent.vue
の両方にcolor
プロップは必要ないので、それを取り除きます。 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
プロパティのみなので、それを渡すだけです。 その後、小道具を使用するときと同じように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
を反応性にした後の変化を表すために、 color
をcolor.value
に変更します。 この時点で、このコンポーネントのテキストのclass
は、親コンポーネントでselectedColor
が変更されるたびに常に変更されます。
テレポート
アプリが使用するロジックのために、コンポーネントを作成してアプリケーションの一部に配置する場合がありますが、アプリケーションの別の部分に表示することを目的としています。 この一般的な例は、画面全体を表示およびカバーすることを目的としたモーダルまたはポップアップです。 このような要素でCSSのposition
プロパティを使用してこの回避策を作成できますが、Vue 3では、Teleportを使用して回避することもできます。
テレポートを使用すると、コンポーネントをドキュメント内の元の位置から取り出し、デフォルトの#app
コンテナーからVueアプリをラップして、使用しているページ上の既存の要素に移動できます。 良い例は、テレポートを使用してヘッダーコンポーネントを#app
内からheader
に移動することです。VueDOMの外部に存在する要素にのみテレポートできることに注意することが重要です。
テレポートコンポーネントは、このコンポーネントの動作を決定する2つの小道具を受け入れます。
-
to
このpropは、クラス名、ID、要素、またはdata- *属性のいずれかを受け入れます。 テレポート要素を動的に変更to
のではなく、:to
propを渡すことで、この値を動的にすることもできます。 -
:disabled
この小道具はBoolean
を受け入れ、要素またはコンポーネントのテレポート機能を切り替えるために使用できます。 これは、要素の位置を動的に変更する場合に役立ちます。
テレポートを使用する理想的な例は次のようになります。
# 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
、topropにheader
の値を指定します。 最後に、このコンポーネントをアプリにインポートします。
# App.vue <template> <router-view /> <app-header></app-header> </template> <script> import appHeader from "@/components/Header.vue"; export default { components: { appHeader, }, }; </script>
このファイルでは、ヘッダーコンポーネントをインポートしてテンプレートに配置し、アプリで表示できるようにします。
ここで、アプリの要素を調べると、ヘッダーコンポーネントがheader
要素内にあることがわかります。
フラグメント
Vue 2.xでは、ファイルのtemplate
に複数のルート要素を含めることは不可能でした。回避策として、開発者はすべての要素を親要素にラップし始めました。 これは深刻な問題のようには見えませんが、開発者がそのような要素をコンテナでラップせずにコンポーネントをレンダリングしたいが、それを行わなければならない場合があります。
Vue 3では、フラグメントと呼ばれる新機能が導入されました。この機能により、開発者はルートテンプレートファイルに複数の要素を含めることができます。 したがって、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>
ここでは、 label
とtype
の2つの小道具を受け入れる単純なフォーム要素コンポーネントがあり、このコンポーネントのテンプレートセクションはdivでラップされています。 これは必ずしも問題ではありませんが、ラベルと入力フィールドをform
要素内に直接配置する場合。 Vue 3を使用すると、開発者はこのコンポーネントを次のように簡単に書き直すことができます。
# inputComponent.vue <template class="testingss"> <label :for="label">{{ label }}</label> <input :type="type" : :name="label" /> </template>
ルートノードが1つしかない場合、属性は常にルートノードに関連付けられ、非プロップ属性とも呼ばれます。 これらは、 props
またはemits
で定義された対応するプロパティを持たないコンポーネントに渡されるイベントまたは属性です。 このような属性の例は、 class
とid
です。 ただし、マルチルートノードコンポーネントのどの要素に帰属するかを明示的に定義する必要があります。
上からinputComponent.vue
を使用することの意味は次のとおりです。
- 親コンポーネントのこのコンポーネントに
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
はコンポーネントに影響を与えません。
- これを修正するには、そのような属性を配布する要素に
v-bind="$attrs"
を追加します。
<template> <label :for="label" v-bind="$attrs">{{ label }}</label> <input :type="type" : :name="label" /> </template>
ここでは、属性をlabel
要素に分散させたい、 awesome__class
をそれに適用したいということをVueに伝えています。 ここで、ブラウザで要素を調べると、クラスがlabel
に追加されていることがわかります。したがって、ラベルの周囲に境界線があります。
グローバルAPI
Vueアプリケーションのmain.js
ファイルにVue.component
またはVue.use
が表示されることは珍しくありませんでした。 これらのタイプのメソッドはグローバルAPIとして知られており、Vue2.xにはかなりの数のメソッドがあります。 この方法の課題の1つは、他のアプリに影響を与えることなく、特定の機能をアプリの1つのインスタンスに分離できないことです(アプリに複数のインスタンスがある場合)。これらはすべて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
に関連付けられていることを示すことはできませんが、代わりに、2つのアプリで両方を使用できます。
Vue 3には、 createApp
の導入によりこのタイプの問題を修正するために、新しいグローバルAPIが付属しています。 このメソッドは、Vueアプリの新しいインスタンスを返します。 アプリインスタンスは、現在のグローバルAPIのサブセットを公開します。 これにより、 Vue
2.xからVueを変更するすべてのAPI(コンポーネント、ミックスイン、ディレクティブ、使用など)が個々のアプリインスタンスに移動され、Vueアプリの各インスタンスが固有の機能を持つことができるようになります。他の既存のアプリに影響を与えることなくそれらを。
これで、上記のコードは次のように書き直すことができます。
const app1 = createApp({}) const app2 = createApp({}) app1.directive('focus', { inserted: el => el.focus() }) app2.mixin({ /* ... */ })
ただし、すべてのアプリ間で共有したい機能を作成することは可能であり、これはファクトリ関数を使用して行うことができます。
イベントAPI
Vuexストアを使用する以外に親子関係を持たないコンポーネント間でデータを渡すために開発者が採用した最も一般的な方法の1つは、イベントバスの使用です。 この方法が一般的である理由の1つは、使い始めるのがいかに簡単かということです。
# 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
ペアを使用して、親コンポーネントから深くネストされた子コンポーネントにデータを渡す方法について説明しました。 また、アプリ内のあるポイントから別のポイントにコンポーネントを再配置して転送する方法についても見てきました。 私たちが見たもう1つのことは、マルチルートノードコンポーネントと、属性が正しく機能するように属性を確実に配布する方法です。 最後に、EventsAPIとGlobalAPIの変更についても説明しました。
その他のリソース
- 「ES6 +でのJavaScriptファクトリ関数」、Eric Elliott、Medium
- 「イベントバスを使用してVueコンポーネント間で小道具を共有する」、Kingsley Silas、CSS-Tricks
- 同じターゲットで複数のテレポートを使用するVue.jsDocs
- 非プロップ属性、Vue.jsドキュメント
- 反応性の操作、Vue.jsドキュメント
teleport
、Vue.jsドキュメント- フラグメント、Vue.jsドキュメント
- 2.x構文、Vue.jsドキュメント