Vue3から加わった新機能のComposition APIで以前までの書き方が一新されてるので、それぞれの違いを羅列。

テンプレートはVue2.xの時と同じです。
よく使うものを優先的に載せていきます。
全文書くのは長くなるので基本形は極力割愛します。

@vue/composition-api@nuxtjs/composition-apiとで書き分けている箇所がいくつかありますが、@nuxtjs@vueをラップしたライブラリなので@vueでできることは@nuxtjsでもできます。

目次

基本形

Option APIの時と比べてオブジェクトでプロパティを指定していくスタイルは変わりません。

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({})
</script>

💚Composition API

<script lang="ts">
import { defineComponent } from '@vue/composition-api'

export default defineComponent({})
</script>

コンポーネントオプション

el

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  el: '#app',
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  el: '#app',
})
</script>

name

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  name: 'vue-component',
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  name: 'vue-component',
})
</script>

components

💛Option API

<script lang="ts">
import Vue from 'vue'
import ComponentA from '@/components/A.vue'

export default Vue.extend({
  components: {
    ComponentA,
  },
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent } from '@vue/composition-api'
import ComponentA from '@/components/A.vue'

export default defineComponent({
  components: {
    ComponentA,
  },
})
</script>

filters

💛Option API

<template>
  <div>
    <input v-model="text" />
    <p>{{ text | toCountLabel }}</p>
  </div>
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  data: () => {
    return {
      text: ''
    }
  },
  filters: {
    toCountLabel (text: string) {
      return `文字数:${text.length}`
    }
  },
})
</script>

💚Composition API

廃止になったので、setup内で完結するのであればcomputed、template内でする必要があれば関数を使う。

<template>
  <div>
    <input v-model="text" />
    <p>{{ toCountLabel(text) }}</p>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api'

export default defineComponent({
  setup () {
    const text = ref('')
    // computedの場合
    // const toCountLabel = computed(() => `文字数:${text.value.length}`)
    const toCountLabel = (text: string) => `文字数:${text.length}`

    return {
      text,
      toCountLabel,
    }
  }
})
</script>

provide

ロジック部分は割愛

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  provide: {
    pageTitle: 'ページタイトル',
  }
})
</script>

💚Composition API

<script lang="ts">
import {
  defineComponent,
  provide,
} from '@vue/composition-api'

export default defineComponent({
  setup () {
    provide(Symbol('pageTitle'), 'ページタイトル')
  }
})
</script>

inject

ロジック部分は割愛

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  inject: ['pageTitle'],
})
</script>

💚Composition API

<script lang="ts">
import {
  defineComponent,
  inject,
} from '@vue/composition-api'

export default defineComponent({
  setup () {
    const uploadDialog = inject(Symbol('pageTitle')) as string

    return {
      uploadDialog
    }
  }
})
</script>

mixins

ロジック部分は割愛

<script lang="ts">
import Vue from 'vue'
import Mixin from './mixins'

export default Vue.extend({
  mixins: [Mixin],
})
</script>

💚Composition API

同様の使い方が可能だがProvide/Injectへの移行が推奨されているので割愛。

props

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  props: {
    count: {
      type: Number,
      default: 0,
    },
  },
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent } from '@vue/composition-api'

type Props = {
  count: number
}

export default defineComponent({
  props: {
    count: {
      type: Number,
      default: 0,
    },
  },
  setup (props: Props) {}
})
</script>

data

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  data: () => {
    return {
      text: '',
      num: null,
      obj: {
        foo: 0,
        bar: false,
      },
    },
  },
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent, ref, toRefs } from '@vue/composition-api'

type State = {
  foo: number
  bar: boolean
}

export default defineComponent({
  setup () {
    const text = ref<string>('')
    const num = ref<number>(0)
    const obj = reactive<State>({
      foo: 0,
      bar: false,
    })

    // setup内での値
    text // { value: '' }
    num // { value: null }
    obj // { foo: 0, bar: false }

    return {
      value,
      ...toRefs(obj),
      // toRefs(obj) => { foo: ref(0), bar: ref(null) }
    }
  }
})
</script>

computed

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  data: () => {
    return {
      text: '',
    },
  },
  computed: {
    textLength() {
      return this.text.length
    },
    computedText: {
      get () {
        return this.text
      },
      set (text) {
        this.text = text
      }
    }
  },
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api'

export default defineComponent({
  setup () {
    const text = ref('')
    const textLength = computed(() => text.value.length)
    const computedText = computed({
      get: () => text.value,
      set: text => {
        text.value = text
      }
    })

    // setup内での値
    valueLength // { value: 0 }
    computedText // { value: '' }

    return {
      valueLength,
      computedText,
    }
  }
})
</script>

watch

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  data: () => {
    return {
      text: '',
      obj: {
        foo: 0,
        bar: false,
      }
    },
  },
  watch: {
    text (newText, oldText) {},
    obj: {
      handler (newObj, oldObj) {},
      { deep: true }
    }
  }
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent, watch, toRefs } from '@vue/composition-api'

type State = {
  foo: number
  bar: boolean
}

export default defineComponent({
  setup () {
    const text = ref('')
    const obj = reactive<State>({
      foo: 0,
      bar: false,
    })

    watch(
      () => text.value,
      (newText, oldText) => {}
    )

    watch(
      () => obj,
      (newObj, oldObj) => {},
      { deep: true }
    )

    return {
      text,
      ...toRefs(obj)
    }
  }
})
</script>

ライフサイクルフック

名前自体変わっているのでComposition APIでの実行例とコメントに変更前の関数名記載。
beforeCreate createdsetupが役割を担っているので実質廃止。

<script lang="ts">
import {
  defineComponent,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onActivated,
  onDeactivated,
  onErrorCaptured
} from '@vue/composition-api'

export default defineComponent({
  setup () {
    onBeforeMount(() => {/* beforeMount */})
    onMounted(() => {/* mounted */})
    onBeforeUpdate(() => {/* beforeUpdate */})
    onUpdated(() => {/* updated */})
    onBeforeUnmount(() => {/* beforeDestroy */})
    onUnmounted(() => {/* destroyed */})
    onActivated(() => {/* activated */})
    onDeactivated(() => {/* deactivated */})
    onErrorCaptured(() => {/* errorCaptured */})
  }
})
</script>

methods

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  methods: {
    handleClick () {}
  }
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent } from '@vue/composition-api'

export default defineComponent({
  setup () {
    const handleClick = () => {}

    return {
      handleClick,
    }
  }
})
</script>

インスタンスプロパティ

$emit

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  props: {
    count: {
      type: Number,
      default: 0,
    },
  },
  methods: {
    handleClick () {
      this.$emit('click', this.count)
    }
  }
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent, SetupContext } from '@vue/composition-api'

type Props = {
  count: number
}

export default defineComponent({
  setup (props: Props, context: SetupContext) {
    const handleClick = () => {
      context.emit('click', props.count)
    }

    return {
      handleClick,
    }
  }
})
</script>

$refs

💛Option API

<template>
  <component ref="form" />
</template>

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  mounted () {
    const form = this.$refs.form
  }
})
</script>

💚Composition API

<template>
  <component ref="formRef" />
</template>

<script lang="ts">
import { defineComponent, SetupContext, onMounted } from '@vue/composition-api'

export default defineComponent({
  setup (_, context: SetupContext) {
    const formRef = ref(null)

    onMounted(() => {
      const form = formRef.value.refs.form
    })

    return {
      formRef,
    }
  }
})
</script>

$route, $router

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  methods: {
    handleClick () {
      this.$router.push(this.$routes.params)
    }
  },
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent, SetupContext } from '@vue/composition-api'
import { useContext, useRouter } from '@nuxtjs/composition-api'

export default defineComponent({
  setup (_, context: SetupContext) {
    const handleClick = () => {
      // @vue
      context.root.$router.push(context.root.$route.params)

      // @nuxtjs
      const { route } = useContext()
      const router = useRouter()
      router.push(route.params)
    }
  }
})
</script>

$store

💛Option API

<script lang="ts">
import Vue from 'vue'

export default Vue.extend({
  mounted () {
    this.$store.dispatch('initialize')
  }
})
</script>

💚Composition API

<script lang="ts">
import { defineComponent, SetupContext, onMounted } from '@vue/composition-api'
import { useStore } from 'vuex'
import { useContext } from '@nuxtjs/composition-api'

export default defineComponent({
  setup (_, context: SetupContext) {
    // @vue
    const store = useStore()

    // @nuxtjs
    const { store } = useContext()

    onMounted(() => {
      store.dispatch('initialize')
      // contextにも含まれる
      context.root.$store.dispatch('initialize')
    })
  }
})
</script>

と、ひたすら書き連ねましたが、以前と比べて書く量は確実に多くなると思います。
しかしComposition APIの強みはロジック単位で分離できるところにあります。
これまではSFCである以上はテンプレートと一緒に!というのが慣習の一つでコンポーネントに依存する処理の共通化はmixinsで賄いましょうというのがVueのカラーだったので、どんな形で進めても最終的にはある程度同じような書き方になっていたのが、設計や思想によって色んなアプローチができるようになりました。

「こ、こいつが俺の知ってるVueなのか・・・?以前とは姿も形も違うぞ・・・」

と思ってしまうやり方も出てくると思います。

また、自由度が高くなるということはその分設計の比重が大きくなるということなので、移行を検討しているのであればただのバージョンアップといった感覚は捨てた方が事故らずに済むと思います。

個人的には「あれ?これReact(hooks)と雰囲気似てきてね?」ってなってるので、これからVueならではのユニークさがもっとでてきてくれたらいいなぁと感じてます🥝

プロフィール画像

ふじわら

よくわからないもので戯れてたら自分のことすらよくわからない人間になってしまいました。

ひっそりYouTubeしてます。