Help us understand the problem. What is going on with this article?

正式リリース前に総予習!! Vue3の変更点まとめ

8月上旬に正式リリース予定とされているVue3の変更点をいち早く理解できるように概要をまとめてみました。それぞれの項目ごとにvuejs/rfc又はVue3 Documentへのリンクを貼っているので索引的に使ってもらえると嬉しいです。

この記事は以下バージョン時点の情報です。

Composition APIの追加

おそらく一番大きな目玉となる変更はこちら。Composition APIという新しいAPIが追加されます(PluginでVue2系でも使用可能です)。
Composition APIはコンポーネントのロジックの柔軟な再構成を可能にする関数ベースのAPI群です。
なお、Vue2系のOptions APIのサポートも継続されるので、Vue2系からのVue3へのバージョンアップ時にComposition APIに書き換える必要はありません。

<!-- Vue3 Composition API -->
<template>
  <form>
    <input type="text" v-model="formState.name" />
    <input type="text" v-model="formState.value" />
    <p>{{ contentLength }}</p>
    <button @click="submit" type="submit">submit</button>
  </form>
</template>

<script lang="ts">
import { defineComponent, computed, ref, onMounted, reactive } from "vue";

export default defineComponent({
  props: {
    maxContentLength: {
      type: String,
      required: true
    }
  },
  setup(props, context) {
    // reactive data
    const formState = reactive({
      name: '',
      content: ''
    })

    // computed
    const contentLength = computed(() => `${formState.content.length} / ${props.maxContentLength}`)

    // emit
    const submit = () => context.emit('submit', formState)

    // lifecycle hook
    onMounted(() => {
      // ....
    })

    return {
      formState,
      contentLength,
      submit
    };
  }
});
</script>

Composition APIについてはこちらの記事でより詳細にまとめています。

先取りVue 3.x !! Composition API を試してみる - Qiita
Vue Composition APIで使えるリアクティブ関連のAPI一覧 - Qiita

Teleportの追加

Teleportは定義したコンポーネントが属するDOMツリーとは別の場所に、まるでテレポートしたかのようにコンポーネントを移動できる機能です。Vue2系でもサードパーティのプラグインLinusBorg/portal-vueで実現されていました。

以下のようにモーダル表示をハンドルするコンポーネントで<teleport>を使うと、toで指定したDOM要素に内部のコンポーネントを移動できます。

<template>
  <div class="container">
    <button @click="toggleModal">toggle modal</button>
    <teleport to="#teleport-target">
      <MyModal v-if="isVisible"/>
    </teleport>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from "vue";

export default defineComponent({
  setup() {
    const isVisible = ref(false);

    const toggleModal = () => {
      isVisible.value = !isVisible.value; };
    return {
      isVisible,
      toggleModal
    };
  }
});
</script>

toで指定できるDOM要素は自身のVueインスタンスをマウントしたDOM要素以外も指定できます。

<html lang="ja">
<body>
  <div id="app"></div> <!-- VueがマウントされるDOM -->
  <div id="teleport-target"></div> <!-- teleportで指定されているDOM。ここにMyModalが表示される-->
  <script src="/dist/main.js"></script>
</body>
</html>

teleportを利用すると、いままでCSSのz-indexで調整していたDOMの重なり順制御を、宣言的に制御できるようになりz-indexの指定に悩まされることはなくなります。
Vue3を入れたらすぐに使いたいと思える実用的な機能ですね。

こちらの記事でも詳細をまとめています。
Vue.js 3.0 の新機能を試す。 〜 Teleport 編〜 - Qiita

Fragmentsの追加

FragmentsはVue2系では実現できなかったmulti-root nodeのコンポーネントを可能にするものです。
Vue2系ではコンポーネントのroot要素は必ず1つという制約がありました。

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

Vue3ではFragmentsのおかげでその制約がなくなり、root要素に複数の要素を記載できます。冗長なdivでのラップなどが不要になります。簡潔で良いですね。

<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

ただ、注意点としてコンポーネントに直接Class指定をしていると、今まで自動的にroot要素にClassを当てられたのですが、それがfragmentsの場合は明示的な指定が必要になるというのがあります。

Suspenseの追加(実験的)

Suspenseは非同期処理が解決されるまでフォールバックコンテンツ(例えばLoading中アイコン)を表示してくれる特別なコンポーネントです。いままで、v-if="loading === true" などの状態変数を使って制御していたものを、状態変数を使わずに簡潔に書くことができます。

Suspenseはまだ実験的な機能で、Vue3.0の段階では仕様が確定しておらず今後変更が入ることも考えられます。Vue 3.1での正式リリースを見越しているとのことです。

<template>
  <Suspense>
    <template #default>
      <AsyncComponents/> <!-- 非同期処理を実行するコンポーネント -->
    </template>
    <template #fallback>
      loading...
    </template>
  </Suspense>
</template>

Suspenseの詳細はこちらにまとめています。
Vue.js 3.0 の新機能を試す。 〜 Suspense 編〜 - Qiita

v-modelの仕様変更

v-modelの機能が拡張され、ひとつの要素に複数のv-modelの設定が可能になります。
Vue2系のv-bind.syncで実現していたようなことがv-modelで実現できるようになるイメージですね。この機能によってv-bind.syncは代替されるので利用できなくなります。

以下v-modelの複数バインドの例です。
v-model:xxxxのxxxのところで明示的にバインドするプロパティを指定しています。

<!-- 親コンポーネント -->
<template>
  <UserForm
   v-model:name="name"
   v-model:message="message"
  />
</template>
<!-- UserFormコンポーネント -->
<template>
  <form>
   <label>name</label>
   <input 
    type="text"
    @input="$emit('update:name', $event.target.value)"
   >
   <label>message</label>
   <input 
    type="text"
    @input="$emit('update:message', $event.target.value)"
   >
  </form>
</template>

<script lang="ts">
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    name: {
      type: String,
      default: ''
    },
    message: {
      type: String,
      default: ''
    }
  },
  setup() { return {} }
})
</script>

また、modifiersの機能が拡張され、.lazy, .number, trimのデフォルトのmodifiers以外に独自に,modifiersを定義できるようになりました。
以下はcapitalizeというmodifiersを設定している例です。v-model:name.capitalize=とすることで入力値にcapitalizeを実行しています。

<!-- 親コンポーネント -->
<template>
  <form>
    <MyName  v-model:name.capitalize="form.name"/>
    <!-- ... -->
  </form>
</template>
<!-- MyNameコンポーネント -->
<template>
  <input
    :value="name"
    @input="updateName"
    type="text"
    name="name"
  />
</template>

<script>
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    name: {
      type: String,
      default: ''
    },
    nameModifiers: {
      default: () => ({})
    }
  },
  setup (props, { emit }) {
    const updateName = event => {
      let val = event.target.value
      if (props.nameModifiers.capitalize) {
        val = val.charAt(0).toUpperCase() + val.slice(1)
      }

      emit('update:name', val)
    }

    return {
      updateName
    }
  }
}
</script>

scoped CSSの仕様変更

scoped CSSで使える擬似クラスの仕様変更・機能追加が行われます。
まず1つ目が::v-deep()でこれは、今まで/deep/として設定していた、子コンポーネントにスタイルを適用するための擬似クラスです。
/deep/の記法がDEPRECATEDとなり、新たに::v-deep()が追加されています。

::v-deep(.foo) {}
/* コンパイル後 */
[v-data-xxxxxxx] .foo {}

2つ目が、::v-slotted()で、これはslotで受け取った親コンポーネントの要素にスタイルを適用するためのものです。Vue3からデフォルトではslotで受け取った要素は、子コンポーネントのスタイルが適用されなくなるため、こちらが追加されました。

::v-slotted(.foo) {}
/* コンパイル後 */
.foo[v-data-xxxxxxx-s] {}

最後が、::v-global()で、これはscoped CSSの中でグローバルなスタイルの定義を可能とするものです。この擬似要素を設定したクラスには[v-data-xxxx]が付与されないため、Scopedではなくグローバルに適応されます。

::v-global(.foo) {}
.foo {}

$attrsの仕様変更

$attrsの機能が拡張され、今まで$listenersで参照していたネイティブイベントや別管理であったclass、styleも全て包括して$attrsから取得できるようになりました。

<MyButton 
  @click="handleClick" 
  @custom="handleCustom" 
  v-model="value"
  type="button"
  class="btn"
/>
// MyButton.vue
$attrs: {
  class: 'btn'
  type: 'button',
  onClick: handleClick,
  onCustom: handleCustom,
  'onUpdate:modelValue': () => { value = payload },
}

そのため、$listenersはDEPRECATEDとなります。(実行時に警告がでます)
$listenersを使って透過的なラッパーコンポーネントを作っていた場合は、$listenersを削除し$attrsへの書き換えが必要です。

<!-- Vue2系 -->
<input v-bind="$attrs" v-on="$liseners" />
<!-- Vue3 -->
<input v-bind="$attrs" />

Global APIの仕様変更

Vueの初期化処理が変わります。createAppでインスタンスを生成し、そのインスタンススコープでusemixinなどが適応されるようになります。
これによってテストケースでのグローバル設定の汚染を防いだり、同一ファイル内で別設定のVueインスタンスの初期化が容易になります。

import { createApp } from 'vue'
import App from './App.vue'

const app = createApp(App) // インスタンスを生成

app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */) // インスタンススコープで設定される
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)

app.config.globalProperties.customProperty = () => {}

app.mount(App, '#app')

filterの廃止

filterは、パイプ演算子|を使って関数適応をlinuxの標準入出力のパイプラインするものですがJavaScriptにはない独自構文で学習コストが増加する、構文解析を複雑にするという理由で削除されます。

filterの処理は通常の関数適応で代替できます。

<!-- filter形式 -->
<p>{{ msg | uppercase | reverse }}</p>

<!-- 通常の関数適応形式 -->
<p>{{ reverse(uppercase(msg)) }}</p>

他の代替方法についてはrfcs/0015-remove-filters.md#alternativesをご覧ください。

Event Emitter系のAPIの廃止

Event Emitter系のAPI($on, $off, $once)が削除されます。
グローバルなイベント管理としてEvent Emitterを仕様している場合は書き換えが必要です。
Event管理の代替にはMittなどの別のEvent Emitterライブラリの利用が推奨されています。

Functional Componentの廃止

Functional Componentはdata等の状態を持たない代わりにレンダリングパフォーマンスの大幅な改善が行えるコンポーネントです。
Vue3ではFunctional Componentを作る際に使用するfunctional: trueのオプションや、<template functional>が削除されます。

Vue3では、通常のコンポーネントとFunctional Componentの性能差は大幅に縮小され、ほとんどのユースケースでは取るに足らないものとなったそうです。

SFC上でfunctionalを使用している場合は、functionalの属性を削除し、attrs、listnersの参照方法の修正が必要です。

<!-- Vue2 functional component -->
<template functional>
  <component
    :is="`h${props.level}`"
    v-bind="attrs"
    v-on="listeners"
  />
</template>

<script>
export default {
  props: ['level']
}
</script>
<!-- Vue3 -->
<template> <!-- functionalを削除 -->
  <component
    v-bind:is="`h${props.level}`"
    v-bind="$attrs" <!-- attrs及びlistnersは$attrsにまとめられる  -->
  />
</template>

<script>
export default {
  props: ['level']
}
</script>

終わりに

以上「正式リリース前に総予習!! Vue3の変更点まとめ」でした。
色々ワクワクするような新機能が追加されていてVue3のリリースが待ち通しいですね。
他にも諸々細かい点が変わってるのでより詳細な変更はvuejs/rfcをご確認ください。

参考

ryo2132
Frontend engineer / フルリモートワーク / 元消防士🚒 / 一児の父 / Ruby / Typescript / Vue.js / Firebase
misoca
クラウド請求管理サービス「Misoca」を開発する、Misoca開発チームです。
https://www.misoca.jp/
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした