BT

最新技術を追い求めるデベロッパのための情報コミュニティ

寄稿

Topics

地域を選ぶ

InfoQ ホームページ ニュース クラスベースAPIから再利用および構成の可能な関数ベースアプローチへと移行したVue 3.0

クラスベースAPIから再利用および構成の可能な関数ベースアプローチへと移行したVue 3.0

ブックマーク

原文(投稿日:2019/07/06)へのリンク

Vueチームは先頃、近くリリースされるVue 3用で導入される予定の、関数ベースのコンポーネントAPIについて説明するRFCを公開した。React Hooksのような関数ベースの、このコンポーネントAPIでは、ロジックをいわゆる"合成関数(composition function)"にカプセル化することで、コンポーネント間での再利用を可能にする。さらに、従来より優れた組み込みTypeScript型推論サポートを、現在は廃止されているClass API RFCでは不可能であった方法で提供している。Vue 3の新APIは、既存のVue 2コンポーネントAPIを代替するものではなく、並列して存在するものになる。

Vueの作成者であるEvan You氏は、関数ベースのコンポーネントAPIを提案する背景となった動機について、次のように説明している。

論理合成(logic composition)は、プロジェクトをスケールアップする場合には、おそらく最も深刻な問題のひとつです。(…)さまざまなタイプのプロジェクトを扱うユーザは、さまざまなニーズに直面します。その中には、オブジェクトベースのAPIを使用して簡単に処理できるものと、できないものがあります。主な例としては、

  1. 複数の論理タスクをカプセル化した、大規模な(数百行の)コンポーネント
  2. 複数のコンポーネント間においてタスクにロジックを共有するニーズ

合成関数へのロジックのカプセル化は、従来のAdvanced Reactivity API RFCおよびDynamic Lifecycle Injections RFCによって有効になる。

先進的なリアクティビティAPIでは、状態の変化を作成し、監視し、対応するためのスタンドアロンAPIが提供される。ドキュメントには、statevaluecomputedwatch APIを示す次の基本的な例が紹介されている。 

import { state, value, computed, watch } from '@vue/observer'

// reactive object
// equivalent of 2.x Vue.observable()
const obj = state({ a: 1 })

// watch with a getter function
watch(() => obj.a, value => {
  console.log(`obj.a is: ${value}`)
})

// a "pointer" object that has a .value property
const count = value(0)

// computed "pointer" with a read-only .value property
const plusOne = computed(() => count.value + 1)

// pointers can be watched directly
watch(count, (count, oldCount) => {
  console.log(`count is: ${count}`)
})

watch(plusOne, countPlusOne => {
  console.log(`count plus one is: ${countPlusOne}`)
})

stateは、リアクティブオブジェクトを生成する。valueは、リアクティブなプリミティブ値をカプセル化したvalueポインタを生成する。computedは、既存のリアクティブstateから派生したリアクティブ計算を扱う。watchは、監視対象のstateが変化した時のエフェクトの実行を可能にする

Dynamic Lifecycle Injections RFCは、動的にコンポーネントライフサイクルフックをインジェクトするAPIを導入するものだ。次の簡単な例は、そのAPIを示している。

import { onMounted, onUpdated, onDestroyed } from 'vue'

export default {
  created() {
    onMounted(() => {
      console.log('mounted')
    })

    onUpdated(() => {
      console.log('updated')
    })

    onDestroyed(() => {
      console.log('destroyed')
    })
  }
}

上のコードサンプルでは、Vueコンポーネントインスタンスが生成された時に、onMountedonUpdatedonDestroyedという3つのVueライフサイクルフックがインスタンスに追加される。

上述した2つのRFCを組み合わせることで、関数ベースのコンポーネントAPIが有効になる。次に示す関数useMouseは、マウスカーソル位置を追跡するためのロジックをカプセル化している。

function useMouse() {
  const x = value(0)
  const y = value(0)
  const update = e => {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return { x, y }
}

次の関数useFetchは、プロパティ(props.id)が変更された時にリモートデータをフェッチするロジックをカプセル化する。

function useFetch(props) {
 const isLoading = value(true)
 const post = value(null)
 
 watch(() => props.id, async (id) => {
   isLoading.value = true
   post.value = await fetchPost(id)
   isLoading.value = false
 })
 
 return {
   isLoading,
   post
 }
}

上述の2つの関数を再利用して、次のようにコンシューマコンポーネントに結合することができる。

<template>
  <div>
    <template v-if="isLoading">Loading...</template>
    <template v-else>
      <h3>{{ post.title }}</h3>
      <p>{{ post.body }}</p>
    </template>
    <div>Mouse is at {{ x }}, {{ y }}</div>
  </div>
</template>

<script>
import { value, watch, onMounted, onUnmounted } from 'vue'
import { fetchPost } from './api'

export default {
 setup(props) {
   return {
     ...useFetch(props),
     ...useMouse()
   }
 }
}
</script>

合成されたコンポーネントでは、リモートデータの取得とマウス位置の表示を、それぞれのロジックをVueで構成関数と呼ばれるもの(useFetchuseMouse)に移譲して行う。

提案されている関数ベースのコンポーネントAPIのもうひとつのメリットは、TypeScriptでの型推論の改善である。TypeScriptで適切な型推論を取得するには、関数呼び出しでコンポーネント定義をラップする必要がある。

import { createComponent } from 'vue'

const MyComponent = createComponent({
  // props declarations are used to infer prop types
  props: {
    msg: String
  },
  setup(props) {
    props.msg // string | undefined

    // bindings returned from setup() can be used for type inference
    // in templates
    const count = value(0)
    return {
      count
    }
  }
})

ドキュメントには次のように記されている。

createComponentは、概念的には2.xのVue.extendに似ていますが、何の処理もせず、型の目的でのみ必要なものです。返されるコンポーネントはオブジェクトそのものですが、VeturとTSXに型情報を提供するように型付されています。単一ファイルコンポーネントを使用している場合、Veturでは、暗黙的にラッパ関数を追加することが可能です。

React Hooksとの時と同じように、新たなRFCは、開発者からは賛意懐疑的な見方が入り交じって受け入れられている。この状況は、Vue開発者の大部分が、関数ベースのコンポーネントAPIの提案理由であるスケールアップの問題に遭遇していない、あるいは別の方法で解決している、という事実が一因するものと考えられる。Martin Sotirov氏が次のように説明している。

認めたくはない事実ですが、このRFCが解決しようとしている問題(論理合成や型サポートの改善)は正当だが、コミュニティの全体が直面している問題ではない、ということだと思います。
私は2016年から、Vueをフロントエンド作業(小規模で一時的なものから、大規模な企業プロジェクトまで)専用に使用していますが、このRFCが解決するような問題に出会ったことがありません。ミックスインの使用が大規模なコードベースで問題化する可能性がある、ということは事実だと思っています(Vuetifyを参照)ので、ミックスインは使用しないようにしています。大規模なモジュール式コードベースを構築するには、もっと良い方法があります。

採用を増やすためにVue 3では、新しいAPIを、純粋に付加的かつ後方互換性を維持した方法で導入している。既存のVueコードを書き換える必要はない(他のRFCで導入された重大な変更は除く)。

Vue.jsは、MITオープンソースライセンスで利用することができる。コントリビューションはVue.jsコントリビューションガイドラインに従うことを条件に,Vue.js GitHubパッケージ経由で受け付けている。

この記事に星をつける

おすすめ度
スタイル

BT