はじめに
liximomo氏が作成されたfunction-apiの実装が本家預かりになったそうなので、改めて利用してみました。vue2.xからpluginという形で利用可能です。
※Evan氏が言う通り、現在RFCでしかもまだ変更が少々入る予定とのこと。利用は慎重に。
vue.js界隈ではココ最近騒動がありまして、キッカケはEvan Youによる function-base API のRFC発表でした。
Published an RFC for function-based component API in Vue. Inspired by React hooks, but rooted in Vue’s reactivity system. Offering better logic composition and better TypeScript support. : https://t.co/WRBVrVQl7q
— Evan You (@youyuxi) 2019年6月9日
このfunctionベースのAPI発表は様々な憶測を呼び、その憶測が(特にredditで)デマのような情報を生み、比較的平和だったコミュニティが結果的に大混乱する形となりました。
私自身の不用意な発言により、更に間違った情報を生んでしまっては元も子もないので、どんな批判があったかや、RFCがそもそも何なのかみたいな話は本記事では一切するつもりはないです。
もし端的に知りたければ、dev.toに投稿されたVue’s Darkest Day - DEV Community 👩💻👨💻 が非常に良くまとまっておりますので、是非ご一読下さい。
また、本記事を読み進める前に、ぜひ本家のRFCを確認してから進めて下さい。設計思想やよくあるQ&A、各種APIの説明が丁寧にまとめられています。
https://github.com/vuejs/rfcs/blob/function-apis/active-rfcs/0000-function-api.md
セットアップ
GitHub - vuejs/vue-function-api: Vue2 plugin for the function-based RFC.
function-api
は現在のところpluginとして提供されてます。
既存もしくは新規のvueプロジェクトにfunction-api
を導入します。
yarn add vue-function-api
次にplugin
として利用するために以下の記述を行います。
私は今回nuxt.jsを利用しているので、
- plugins
- function-api.js
としてpluginの記述を行いました。
import Vue from 'vue'
import { plugin } from 'vue-function-api'
Vue.use(plugin)
nuxt.jsでpluginを有効化するために、configファイルに読み込むファイルを記述します。
/*
** Plugins to load before mounting the App
*/
plugins: ['~/plugins/function-api.js'],
これで準備は完了です。
利用方法
ガッとトップページを書き換えてみました。
<template>
<div>
<span>count is {{ count }}</span>
<span>plusOne is {{ plusOne }}</span>
<button @click="increment">
count++
</button>
</div>
</template>
<script>
import { value, computed, watch, onMounted } from 'vue-function-api'
export default {
setup() {
// reactive state
const count = value(0)
// computed state
const plusOne = computed(() => count.value + 1)
// method
const increment = () => {
count.value++
}
// watch
watch(
() => count.value * 2,
(val) => {
console.log(`count * 2 is ${val}`)
}
)
// lifecycle
onMounted(() => {
console.log(`mounted`)
})
// expose bindings on render context
return {
count,
plusOne,
increment
}
}
}
</script>
まず setup() という新たなコンポーネントオプションが導入されました。ここにコンポーネントが持つ値、機能、ライフサイクルなどを定義していきます。setup(props, context)
という引数を取ります。 context
の一覧はこちらから確認すると良いでしょう。
その他のコンポーネントライフサイクルやcomputed
、watch
についても解説があります。特にRFCのWatcher部分は良く観ておいたほうが良いかもしれません。
stateとvalueの違い
所見では、state
とvalue
の違いがいまいちわかりません。state
の正体はVue.Observable
というのはわかりました。しかしどちらもリアクティブだし、なぜこんなものを2つ用意する必要があったのでしょう?その答えは #why-do-we-need-value-wrappers こちらにありました。
数値や文字列などのJavaScriptのプリミティブ値は、参照によって渡されません。関数からプリミティブ値を返すということは、受け取った関数は、元の値が変更または置換されたときに最新の値を読み取ることができないということです。
Value wrapperは、任意の値型に対して変更可能かつリアクティブ参照を渡す方法を提供するため、重要です。これにより、コンポジション関数は、状態を管理するロジックをカプセル化し、状態を追跡可能な参照としてコンポーネントに戻すことができます。
なるほど読んで納得、当たり前といえば当たり前な気がします。というわけでこんなテストコード
<template>
<div>
<span>count is {{ v1 }}</span>
<span>state is {{ v2 }}</span>
<button @click="increment">count++</button>
</div>
</template>
<script>
import { value, state } from 'vue-function-api'
export default {
setup() {
// reactive state
const v1 = value(0)
// あえてプリミティブ型を入れてみる
const v2 = state(0)
// method
const increment = () => {
v1.value++
v2++ //プリミティブな値をincrement
}
// expose bindings on render context
return {
v1,
v2,
increment
}
}
}
</script>
このようなエラーが発生しました。ま、当然といえば当然か。
さらに続き
Value Wrapperは非プリミティブ値を保持することもでき、ネストされたすべてのプロパティをリアクティブにします。オブジェクトや配列などの非プリミティブ値を値ラッパー内に保持すると、値を完全に新しい値に置き換えることができます。
const numbers = value([1, 2, 3])
// replace the array with a filtered copy
numbers.value = numbers.value.filter(n => n > 1)
まとめ
functionsの実装内容は下記から確認可能です。
https://github.com/vuejs/vue-function-api/tree/master/src/functions
個人的にはとても好きな実装なので、待ってました、という感じです。引き続き学習を進めていくのと、個人プロジェクトはfunction-api
ベースに切り替えてもいいかなーと思っています。