Edited at

Vue3.xで導入予定のfunction APIを利用してみる


はじめに

liximomo氏が作成されたfunction-apiの実装が本家預かりになったそうなので、改めて利用してみました。vue2.xからpluginという形で利用可能です。

※Evan氏が言う通り、現在RFCでしかもまだ変更が少々入る予定とのこと。利用は慎重に。


vue.js界隈ではココ最近騒動がありまして、キッカケはEvan Youによる function-base API のRFC発表でした。

この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の記述を行いました。


function-api.js

import Vue from 'vue'

import { plugin } from 'vue-function-api'

Vue.use(plugin)


nuxt.jsでpluginを有効化するために、configファイルに読み込むファイルを記述します。


nuxt.config.js

  /*

** Plugins to load before mounting the App
*/

plugins: ['~/plugins/function-api.js'],

これで準備は完了です。


利用方法

ガッとトップページを書き換えてみました。


pages/index.vue

<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の一覧はこちらから確認すると良いでしょう。

その他のコンポーネントライフサイクルやcomputedwatchについても解説があります。特にRFCのWatcher部分は良く観ておいたほうが良いかもしれません。


stateとvalueの違い

所見では、statevalueの違いがいまいちわかりません。stateの正体はVue.Observableというのはわかりました。しかしどちらもリアクティブだし、なぜこんなものを2つ用意する必要があったのでしょう?その答えは #why-do-we-need-value-wrappers こちらにありました。


数値や文字列などのJavaScriptのプリミティブ値は、参照によって渡されません。関数からプリミティブ値を返すということは、受け取った関数は、元の値が変更または置換されたときに最新の値を読み取ることができないということです。

Value wrapperは、任意の値型に対して変更可能かつリアクティブ参照を渡す方法を提供するため、重要です。これにより、コンポジション関数は、状態を管理するロジックをカプセル化し、状態を追跡可能な参照としてコンポーネントに戻すことができます。


なるほど読んで納得、当たり前といえば当たり前な気がします。というわけでこんなテストコード


index.vue

<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>


25066DEE-F7C5-464D-A3A9-0203162C514D.png

このようなエラーが発生しました。ま、当然といえば当然か。

さらに続き


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ベースに切り替えてもいいかなーと思っています。