🧱

「Options API は Composition API を土台にしている」ってどういうこと?

はじめに

Vue.jsのドキュメントのイントロダクションページに「Options API は Composition API を土台にしています」と書かれています。
これは具体的に何を表しているのでしょうか。

どちらのスタイルの API でも、よくあるユースケースは全面的にカバーされます。両者はインターフェースが異なるものの、それを支える基盤のシステムはまったく同じです。事実として、Options API は Composition API を土台にしています!Vue に関する基本的な考え方と必要な知識は、2 つのスタイル間で共通です。

https://ja.vuejs.org/guide/introduction.html#which-to-choose

Vue.jsの実装を追っていくと、Options APIの各オプションがComposition APIの呼び出しとして変換されて処理されているということがわかります。
この実装がドキュメントで「土台にしている」と表現していた部分です。
本記事ではこの変換処理について、Vue.jsの実装を確認しながら変換の実体を確かめます。

おさらい: Options APIとComposition API

Vue.jsでは2つのスタイルでコンポーネントを作成できます。
Options APIとComposition APIです。
実際にコードを読み始めるまえに、Vue.jsのドキュメントを引用しながらこれらの違いについておさらいします。

Options API

https://ja.vuejs.org/guide/introduction.html#options-api

  • オブジェクトを用いてコンポーネントのロジックを定義する
  • 定義するオブジェクトにはdatamethodsmountedなどのオプションがある
  • これらのオプションで定義されたプロパティにはthisを介してアクセスする
Options APIのコード例
<script>
export default {
  // data() で返すプロパティはリアクティブな状態になり、
  // `this` 経由でアクセスすることができます。
  data() {
    return {
      count: 0
    }
  },

  // メソッドの中身は、状態を変化させ、更新をトリガーさせる関数です。
  // 各メソッドは、テンプレート内のイベントハンドラーにバインドすることができます。
  methods: {
    increment() {
      this.count++
    }
  },

  // ライフサイクルフックは、コンポーネントのライフサイクルの
  // 特定のステージで呼び出されます。
  // 以下の関数は、コンポーネントが「マウント」されたときに呼び出されます。
  mounted() {
    console.log(`The initial count is ${this.count}.`)
  }
}
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

Composition API

https://ja.vuejs.org/guide/introduction#composition-api

  • 通常のJSでコンポーネントのロジックを定義する
  • (Vue.jsの)各種API関数はインポートして利用する
  • scriptsetupという属性を付けることでComposition APIを有効化する
Composition APIのコード例
<script setup>
import { ref, onMounted } from 'vue'

// リアクティブな状態
const count = ref(0)

// 状態を変更し、更新をトリガーする関数。
function increment() {
  count.value++
}

// ライフサイクルフック
onMounted(() => {
  console.log(`The initial count is ${count.value}.`)
})
</script>

<template>
  <button @click="increment">Count is: {{ count }}</button>
</template>

Options APIにはどんなAPIがあるのか

Vue.jsのドキュメントを参照して、Options APIにはどのようなAPIがあるのかを把握します。
Vue.jsの実装を確認する際にはこれらがどのように処理されているのかを知ることで「土台」の意味を確かめていきます。
ドキュメントの分類に沿ってそれぞれを簡単に説明します。

https://ja.vuejs.org/api/

オプション: 状態

コンポーネントが持つリアクティブな状態を宣言するためのオプションです。
コンポーネント内で扱う値を記述します。

  • data
  • props
  • computed
  • methods
  • watch
  • emits
  • expose
状態オプションの利用例
export default {
  data() {
    return { a: 1 }
  },
  props: ['size', 'myMessage'],
  computed: {
    aDouble() {
      return this.a * 2
    },
  },
  methods: {
    plus() {
      this.a++
    }
  },
  watch: {
    a(val, oldVal) {
      console.log(`new: ${val}, old: ${oldVal}`)
    },
  },
  emits: ['check'],
  expose: ['publicMethod'],
}

オプション: レンダリング

Vueがコンポーネントをレンダリングする際の挙動を制御するためのオプションです。
テンプレート、レンダリング関数などをどのように扱うかを指定します。

  • template
  • render
  • compilerOptions
  • (slots)

slotsは型付けのためのプロパティなので特に処理はされていません。

レンダリングオプションの利用例
export default {
  template: `<div>{{ msg }}</div>`,
  render() {
    return h('div', this.msg)
  },
  compilerOptions: {
    isCustomElement: (tag) => tag === 'my-element',
  },
}

オプション: ライフサイクル

ライフサイクルフックを宣言するためのオプションです。
コンポーネントのライフサイクル上の各時点で実行したい処理を登録できます。

  • beforeCreate
  • created
  • beforeMount
  • mounted
  • beforeUpdate
  • updated
  • beforeUnmount
  • unmounted
  • errorCaptured
  • renderTracked
  • renderTriggered
  • activated
  • deactivated
  • serverPrefetch
ライフサイクルオプションの利用例
export default {
  beforeCreate() {
    console.log('beforeCreate')
  },
  created() {
    console.log('created')
  },
  beforeMount() {
    console.log('beforeMount')
  },
  mounted() {
    console.log('mounted')
  },
  beforeUpdate() {
    console.log('beforeUpdate')
  },
  updated() {
    console.log('updated')
  },
  beforeUnmount() {
    console.log('beforeUnmount')
  },
  unmounted() {
    console.log('unmounted')
  },
  errorCaptured(err, instance, info) {
    console.log('errorCaptured', err, info)
    return false
  },
  renderTracked(e) {
    console.log('renderTracked', e)
  },
  renderTriggered(e) {
    console.log('renderTriggered', e)
  },
  activated() {
    console.log('activated')
  },
  deactivated() {
    console.log('deactivated')
  },
  async serverPrefetch() {
    console.log('serverPrefetch')
  },
}

オプション: 合成

コンポーネントに外部の機能や依存を注入、合成するためのオプションです。
継承や依存注入を行い、コードを疎結合な形で組み立てます。

  • provide
  • inject
  • mixins
  • extends
合成オプションの利用例
import { myMixin } from './myMixin'
import BaseButton from './BaseButton.vue'
export default {
  provide: {
    theme: 'dark',
  },
  inject: ['theme'],
  mixins: [myMixin],
  extends: BaseButton,
}

オプション: その他

コンポーネントに設定する補助的なオプションです。

  • name
  • inheritAttrs
  • components
  • directives
その他のオプションの利用例
import ChildComponent from './ChildComponent.vue'
export default {
  name: 'MyComponent',

  inheritAttrs: false,

  components: {
    ChildComponent,
  },

  directives: {
    autofocus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}

コンポーネントインスタンス

コンポーネントインスタンスの内部状態にアクセスするための特別なプロパティ、メソッド群です。
this(コンポーネントインスタンス)のプロパティとして公開されていて、コード内で参照できます。

  • $data
  • $props
  • $el
  • $options
  • $parent
  • $root
  • $slots
  • $refs
  • $attrs
  • $watch()
  • $emit()
  • $forceUpdate()
  • $nextTick()
コンポーネントインスタンスの利用例
export default {
  data() {
    return { count: 0 }
  },
  props: {
    msg: String,
  },
  emits: ['mounted-event'],

  mounted() {
    console.log('$data:', this.$data.count)
    console.log('$props:', this.$props.msg)
    console.log('$el:', this.$el)
    console.log('$options:', this.$options.name)
    console.log('$parent:', this.$parent)
    console.log('$root:', this.$root)
    console.log('$slots:', this.$slots)
    console.log('$refs:', this.$refs)
    console.log('$attrs:', this.$attrs)

    this.$watch('count', (n, o) => {
      console.log('$watch → count changed:', n)
    })
    this.$emit('mounted-event', { ready: true })
    this.$forceUpdate()
    this.$nextTick(() => {
      console.log('$nextTick: DOM更新後に実行されました')
    })
  },
}

コンポーネント初期化がどのように行われているのか

Vue.jsの基本的な情報を整理したところで実装を確認していきます。
createApp()が実行され、コンポーネントが初期化されるまでにどのような処理をたどっているのかをざっくりと把握します。

ざっくりと流れをまとめると以下のようになります。
コンポーネントのプロパティに関係する部分を中心に処理を整理します。

上記の流れから必要そうな関数の実行順をピックアップすると以下のようになります。

Vue のコンポーネント生成フローのざっくりとした流れを示す図。createComponentInstance から applyOptions までの処理順を並べている。

これでコンポーネントの初期化が完了します。

より詳細な流れ
  1. createApp()を実行
  2. createApp()内でensureRenderer()を実行
  3. ensureRenderer()内でcreateRenderer()を実行
  4. createRenderer()内でbaseCreateRenderer()を実行
  5. baseCreateRenderer()createAppとしてcreateAppAPI(render, hydrate)の結果を返却
    1. baseCreateRenderer内でrender()を定義
      1. baseCreateRenderer()で以下を定義
        1. patch()
        2. processComponent()
        3. mountComponent()
      2. render()内でpatch()を実行
      3. patch()内でprocessComponent()を実行
      4. processComponent()内でmountComponent()を実行
      5. mountComponent()内でcreateComponentInstance()を実行
      6. mountComponent()内でsetupComponent()を実行
      7. mountComponent()内でsetupRenderEffect()を実行
  6. createAppAPI()createApp()を定義し返却
    1. createApp()appオブジェクトを返却
    2. appオブジェクトにmount()を登録
    3. mount()内でrender()実行
  7. (エントリポイントの方の)createApp()内でapp.mount()を実行
  8. setupComponent()内でinitProps()を実行
  9. setupComponent()内でinitSlots()を実行
  10. setupComponent()内でsetupStatefulComponent()を実行
  11. PublicInstanceProxyHandlersをハンドラに、コンポーネントのインスタンスをプロキシでラップする
  12. コンポーネントにsetup()が登録されている場合、setupStatefulComponent()内でコンポーネントのsetup()を実行
  13. コンポーネントにsetup()が登録されている場合、setupStatefulComponent()内でcreateSetupContext()を実行
  14. コンポーネントにsetup()が登録されている場合、setupStatefulComponent()内でhandleSetupResult()を実行
    1. handleSetupResult()内でfinishComponentSetup()を実行
  15. コンポーネントにsetup()が登録されていない場合、setupStatefulComponent()内でfinishComponentSetup()を実行
  16. Options APIが含まれる場合、finishComponentSetup()内でapplyOptions()を実行

Options APIのプロパティの処理を確認する

コンポーネントの初期化処理を詳しく見ていくと、applyOptions()でコンポーネントに登録したオプションの処理を行っています。
多くの処理をここで行っている一方、それ以前、それ以降に処理されているAPIもあるため1つずつ確認していきます。

オプション: 状態

data

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()内でreactive()を実行しています。
これにより、Options APIのdataオプションはComposition APIのreactive()によってリアクティブ化されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L636

props

Composition APIとOptions APIの両方で同じ処理が行われています。

setupComponent()内のinitProps()で処理され、コンポーネントインスタンスに登録されています。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L811

computed

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()computed()を実行しています。
これにより、Options APIのcomputedオプションはComposition APIのcomputed()によって処理されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L678-L681

methods

Composition APIには存在しません。

それぞれのAPIで根本的に違う処理が行われています。
Options APIの場合はapplyOptions()内でコンポーネントインスタンスのctxプロパティに登録され、利用箇所で呼び出された際に実行されます。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L604

Composition APIの場合はsetup()内に書かれているため、setupStatefulComponent()でコンポーネントのsetup()が実行されるタイミングで処理されます。

watch

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()watch()を実行しています。
これにより、Options APIのwatchオプションはComposition APIのwatch()によって処理されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L885-L914

emits

Composition APIとOptions APIの両方で同じ処理が行われています。

createComponentInstance()でコンポーネントインスタンスに登録しています。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L647

expose

登録方法に違いはありますが、Composition APIとOptions APIの両方でコンポーネントインスタンスのexposeに値を登録しています。

Options APIではapplyOptions()でコンポーネントインスタンスのexposedプロパティにgetter、setterを登録します。
これは、Options APIのexposeプロパティに設定する値がキー名の配列であり、実際の値を取得するロジックとともに登録しなければならないからです。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L754-L761

Composition APIではexpose()の引数として渡された値をコンポーネントインスタンスのexposedプロパティに登録します。
expose()の引数には関数や変数自体を値としてセットしたオブジェクトを渡すため、そのまま登録できます。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L1149

オプション: レンダリング

template

Composition APIとOptions APIの両方で同じ処理が行われています。

finishComponentSetup()でコンポーネントのテンプレートをコンパイルし、render関数をコンポーネントインスタンスに登録しています。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L1038

render

Composition APIとOptions APIの両方で同じ処理が行われています。

finishComponentSetup()でrender関数をコンポーネントインスタンスに登録しています。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L1045

compilerOptions

Composition APIとOptions APIの両方で同じ処理が行われています。

finishComponentSetup()内で、コンポーネントで設定したオプションを利用してコンパイルし、render関数をコンポーネントインスタンスに登録しています。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L1038

slots

型付けのためのプロパティなので特に処理は行われていません。

オプション: ライフサイクル

beforeCreate

Composition APIには存在しません。

applyOptions()内で他のオプションにアクセスするより前にbeforeCreateを実行しています。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L529

created

Composition APIには存在しません。

applyOptions()内ですべての状態オプションが初期化された直後にcreatedを実行しています。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L710

beforeMount

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onBeforeMount()を実行しています。
これにより、Options APIのbeforeMountフックはComposition APIのonBeforeMount()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L724

mounted

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onMounted()を実行しています。
これにより、Options APIのmountedフックはComposition APIのonMounted()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L725

beforeUpdate

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onBeforeUpdate()を実行しています。
これにより、Options APIのbeforeUpdateフックはComposition APIのonBeforeUpdate()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L726

updated

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onUpdated()を実行しています。
これにより、Options APIのupdatedフックはComposition APIのonUpdated()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L727

beforeUnmount

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onBeforeUnmount()を実行しています。
これにより、Options APIのbeforeUnmountフックはComposition APIのonBeforeUnmount()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L733

unmounted

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onUnmounted()を実行しています。
これにより、Options APIのunmountedフックはComposition APIのonUnmounted()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L734

errorCaptured

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onErrorCaptured()を実行しています。
これにより、Options APIのerrorCapturedフックはComposition APIのonErrorCaptured()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L730

renderTracked

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onRenderTracked()を実行しています。
これにより、Options APIのrenderTrackedフックはComposition APIのonRenderTracked()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L731

renderTriggered

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onRenderTriggered()を実行しています。
これにより、Options APIのrenderTriggeredフックはComposition APIのonRenderTriggered()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L732

activated

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onActivated()を実行しています。
これにより、Options APIのactivatedフックはComposition APIのonActivated()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L728

deactivated

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onDeactivated()を実行しています。
これにより、Options APIのdeactivatedフックはComposition APIのonDeactivated()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L729

serverPrefetch

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()onServerPrefetch()を実行しています。
これにより、Options APIのserverPrefetchフックはComposition APIのonServerPrefetch()によって登録されていることになります。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L735

オプション: 合成

provide

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()provide()を実行しています。
これにより、Options APIのprovideプロパティはComposition APIのprovide()によって注入できる値をセットできます。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L705

inject

Options APIの処理の中でComposition APIの関数を実行しています。

applyOptions()inject()を実行しています。
これにより、Options APIのinjectプロパティはComposition APIのinject()によって注入するプロパティを宣言できます。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L805-L809

mixins

Composition APIには存在しません。

Options APIでは、applyOptions()内で実行されているresolveMergedOptions()内で処理されています。
resolveMergedOptions()ではミックスインオブジェクトのオプションを現在のコンポーネントオプションに統合しています。
これにより、以降は各オプションを処理することでmixinsに指定したオプションについても処理されます。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L983-L985

extends

Composition APIには存在しません。

Options APIでは、applyOptions()内で実行されているresolveMergedOptions()内で処理されています。
resolveMergedOptions()では継承元コンポーネントのオプションを現在のコンポーネントオプションに統合しています。
これにより、以降は各オプションを処理することでextendsに指定したオプションについても処理されます。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L980

オプション: その他

name

Composition APIとOptions APIの両方で同じ処理が行われています。

createComponentInstance()でコンポーネントインスタンスに登録しています。
.typeにコンポーネント定義オブジェクトの一部として保持されます。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L620

inheritAttrs

Composition APIとOptions APIの両方で同じ処理が行われています。

createComponentInstance()でコンポーネントインスタンスに登録しています。

https://github.com/vuejs/core/blob/c13e674fb9f92ab9339d28a862d18de460faf56e/packages/runtime-core/src/component.ts#L657

Options APIの場合、applyOptions()で再度代入されています。
これはmixinsオプションやextendsオプションによって新たに統合されたオプションを追加するためです。
これらのオプションがない場合は同じ処理が行われています。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L773

components

Composition APIには存在しません。

それぞれのAPIで根本的に違う処理が行われています。
Options APIの場合はapplyOptions()内でコンポーネントインスタンスのcomponentsプロパティに登録されます。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L777

Composition APIの場合はsetup()内に書かれているため、setupStatefulComponent()でコンポーネントのsetup()が実行されるタイミングで実行されます。

directives

Composition APIには存在しません。

それぞれのAPIで根本的に違う処理が行われています。
Options APIの場合はapplyOptions()内でコンポーネントインスタンスのdirectivesプロパティに登録されます。

https://github.com/vuejs/core/blob/2dbe30177fd3633e06a5e0f243bcf3c238962a57/packages/runtime-core/src/componentOptions.ts#L778

Composition APIの場合はsetup()内に書かれているため、setupStatefulComponent()でコンポーネントのsetup()が実行されるタイミングで実行されます。

コンポーネントインスタンス

$data

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$dataプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントインスタンスのdataプロパティの値が返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L371

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$props

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$propsプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントインスタンスのpropsプロパティの値が返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L372

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$el

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$elプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントインスタンスのvnode.elプロパティの値が返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L370

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$options

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$optionsプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントの全てのオプションをマージした値が返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L380

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$parent

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$parentプロパティへアクセスされたときの処理を行っています。
このとき、親コンポーネントのインスタンスが返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L376

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$root

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$rootプロパティへアクセスされたときの処理を行っています。
このとき、ルートコンポーネントのインスタンスが返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L377

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$slots

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$slotsプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントのスロットオブジェクトが返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L374

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$refs

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$refsプロパティへアクセスされたときの処理を行っています。
このとき、テンプレート参照のオブジェクトが返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L375

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$attrs

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$attrsプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントの属性オブジェクトが返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L373

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L479

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$watch()

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$watchプロパティへアクセスされたときの処理を行っています。
このとき、instanceWatch関数が返却されるように処理されています。この関数内部ではwatch()が実行されます。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L387

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$emit()

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$emitプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントインスタンスのemitメソッドが返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L379

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$forceUpdate()

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$forceUpdateプロパティへアクセスされたときの処理を行っています。
このとき、コンポーネントを強制的に再レンダリングする関数が返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L381-L385

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

$nextTick()

Composition APIとOptions APIの両方で同じ処理が行われています。

PublicInstanceProxyHandlersで、プロキシ上で$nextTickプロパティへアクセスされたときの処理を行っています。
このとき、次のDOM更新サイクル後に実行されるコールバック関数をスケジュールするnextTick関数が返却されるように処理されています。

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L386

https://github.com/vuejs/core/blob/b8aab3d2097db7c447da0ecc2e36784ba23febde/packages/runtime-core/src/componentPublicInstance.ts#L474-L485

まとめ

本記事では「Options API は Composition API を土台にしている」という記述の意味を、Vue.jsの実装を確認しながら検証しました。
Options APIのコンポーネント初期化処理を確認し、処理の流れの中から「土台にしている」と考えられる場所を探しました。

処理の方法で分類すると、

  • Composition APIの関数を内部で利用しているもの
  • 両APIで共通の処理が行われているもの
  • Options API特有の処理が行われているもの

がありました。

1. Composition APIの関数を内部で利用しているもの

applyOptions()関数内で対応するComposition APIの関数を呼び出すことで実装されています。
多くのオプションがこのカテゴリに該当しています。

オプション: 状態

  • data: reactive()を呼び出してリアクティブ化
  • computed: computed()を呼び出して処理
  • watch: watch()を呼び出して処理

オプション: ライフサイクル

  • beforeMount: onBeforeMount()を呼び出して登録
  • mounted: onMounted()を呼び出して登録
  • beforeUpdate: onBeforeUpdate()を呼び出して登録
  • updated: onUpdated()を呼び出して登録
  • beforeUnmount: onBeforeUnmount()を呼び出して登録
  • unmounted: onUnmounted()を呼び出して登録
  • errorCaptured: onErrorCaptured()を呼び出して登録
  • renderTracked: onRenderTracked()を呼び出して登録
  • renderTriggered: onRenderTriggered()を呼び出して登録
  • activated: onActivated()を呼び出して登録
  • deactivated: onDeactivated()を呼び出して登録
  • serverPrefetch: onServerPrefetch()を呼び出して登録

オプション: 合成

  • provide: provide()を呼び出して注入する値をセット
  • inject: inject()を呼び出して注入するプロパティを宣言

2. 両APIで共通の処理が行われているもの

Options APIの場合のみ実行されるapplyOptionsの前後で実行され、両APIともに同じ場所で処理されています。

オプション: 状態

  • props: initProps()でコンポーネントインスタンスに登録
  • emits: createComponentInstance()でコンポーネントインスタンスに登録
  • expose: コンポーネントインスタンスのexposedプロパティに登録(登録方法は異なる)

オプション: レンダリング

  • template: finishComponentSetup()でテンプレートをコンパイルし、render関数をコンポーネントインスタンスに登録
  • render: finishComponentSetup()でrender関数をコンポーネントインスタンスに登録
  • compilerOptions: finishComponentSetup()でコンポーネントに設定したオプションを利用してコンパイル
  • slots: 型付けのためのプロパティなので特に処理は行われていない

オプション: その他

  • name: createComponentInstance()でコンポーネントインスタンスに登録
  • inheritAttrs: createComponentInstance()でコンポーネントインスタンスに登録(Options APIではapplyOptions()でも再度代入)

コンポーネントインスタンス

  • $data: PublicInstanceProxyHandlersでコンポーネントインスタンスのdataプロパティの値が返却される
  • $props: PublicInstanceProxyHandlersでコンポーネントインスタンスのpropsプロパティの値が返却される
  • $el: PublicInstanceProxyHandlersでコンポーネントインスタンスのvnode.elプロパティの値が返却される
  • $options: PublicInstanceProxyHandlersでコンポーネントの全てのオプションをマージした値が返却される
  • $parent: PublicInstanceProxyHandlersで親コンポーネントのインスタンスが返却される
  • $root: PublicInstanceProxyHandlersでルートコンポーネントのインスタンスが返却される
  • $slots: PublicInstanceProxyHandlersでコンポーネントのスロットオブジェクトが返却される
  • $refs: PublicInstanceProxyHandlersでテンプレート参照のオブジェクトが返却される
  • $attrs: PublicInstanceProxyHandlersでコンポーネントの属性オブジェクトが返却される
  • $watch(): PublicInstanceProxyHandlersinstanceWatch関数が返却され、内部でwatch()が実行される
  • $emit(): PublicInstanceProxyHandlersでコンポーネントインスタンスのemitメソッドが返却される
  • $forceUpdate(): PublicInstanceProxyHandlersでコンポーネントを強制的に再レンダリングする関数が返却される
  • $nextTick(): PublicInstanceProxyHandlersnextTick関数が返却される

3. Options API特有の処理が行われているもの

Composition APIには同じ粒度で対応するAPIがないオプションについては特有の処理がapplyOptions()で実行されています。

オプション: 状態

  • methods: コンポーネントインスタンスのctxプロパティに登録され、利用箇所で呼び出された際に実行される

オプション: ライフサイクル

  • beforeCreate: applyOptions()内で他のオプションにアクセスするより前に実行
  • created: applyOptions()内ですべての状態オプションが初期化された直後に実行

オプション: 合成

  • mixins: applyOptions()内のresolveMergedOptions()内で設定したオプションをコンポーネントオプションに統合し、後続処理で一緒に処理
  • extends: applyOptions()内のresolveMergedOptions()内で設定したオプションをコンポーネントオプションに統合し、後続処理で一緒に処理

オプション: その他

  • components: applyOptions()内でコンポーネントインスタンスのcomponentsプロパティに登録
  • directives: applyOptions()内でコンポーネントインスタンスのdirectivesプロパティに登録

結論

多くのAPIで、初期化処理の中で内部的にComposition APIの関数を呼び出していることがわかりました。
Composition APIの関数を呼び出していないAPIについては、共通処理で初期化処理が完結しているもの、Options APIのみに存在する機能のものでした。
これらを合わせて、ドキュメントでは「Options API は Composition API を土台にしている」という説明が行われていたと考えられます。

Vue・Nuxt 情報が集まる広場 / Plaza for Vue・Nuxt.

Discussion