Edited at

Vue Function API で簡単に view と logic を分離しよう

書いた日: 19/8/9


TL;DR

・だいたい custom hooks だ

・view と logic の分離がすごい簡単

・さよなら render props、scoped slots、HoC、function as children、mixin!

vue-function-api、view と logic の分けやすさ、custom hooks が vue でもできる事を喜ぶものなんだなーという印象です。ありがたい。


インストール

Vue2.6以降の環境を用意して

$ yarn add vue-function-api

で、ルートとなる js ファイルに

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

Vue.use(plugin)

を追記すれば準備完了です。


改変元のソースを用意する

新しい書き方を学ぶには

既存コードの改変がとっつきやすいです。

お決まりの Todo アプリで行きます。まずはテンプレートを用意しましょう。

おまえは今まで作った Todo アプリの数をおぼえているのか?

<script>

export default {
data:() => ({
todoList: [],
tempTodoName: '',
taskId: 0
}),
computed: {
itemCount() {
return this.todoList.length
}
},
methods: {
updateTaskName(name) {
this.tempTodoName = name
},
addTodoItem() {
this.todoList = [
...this.todoList,
{
id: this.taskId,
name: this.tempTodoName,
done: false
}
];
this.tempTodoName = ''
this.taskId = this.taskId + 1
},
removeTodoItem(id) {
this.todoList = this.todoList.filter(item => item.id !== id);
},
toggleDoneStatus(id){
this.todoList = this.todoList.map(item =>
item.id === id ? { ...item, done: !item.done } : item
);
}
}
}
</script>

動作確認が取れたら vue-function-api で書き換えてみましょう。


vue-function-api での書き換え

vue-function-api で提供される API は

vue に新しく追加された setup() の中で使います。


data(){} の書き換え

vue-function-api から value()をインポートして使います。今までのdata() { return {} }に代わって、値ごとに value を使って定義します。

value()でラップした値にアクセスする時は 変数名.value です。

// before

export default {
data:() => ({
todoList: [],
tempTodoName: '',
taskId: 0
}),
...
}

// after

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

const useTodo = () => {
const todoList = value([]); // todoList.value = [];
const tempTodoName = value("");
const taskId = value(0);

return {
todoList,
tempTodoName,
taskId
}
}

export default {
setup() {
...useTodo()
}
}

すでに嬉しいですね。

何が嬉しいって data 部分がただの関数としてコンポーネントの外に切り出せています。


computed の書き換え

computed も vue-function-api からインポートして使います。

// before

computed: {
itemCount() {
return this.todoList.length
}
}

// after

const useTodo = () => {
...
const itemCount = computed(() => todoList.value.length);

return {
...
itemCount
}
}

export default {
setup() {
...useTodo()
}
}

前述の通り、value()の値にアクセスする時は 変数名.value です。


methods を書き換える

methods は特にラッパーが提供されていません。

変数名.value プロパティを更新する至ってふつうの関数として書けます。

const useTodo = () => {

...
const updateTaskName = name => { tempTodoName.value = name };

const incrementTaskId = () => { taskId.value = taskId.value + 1 };

const addTodoItem = () => {
todoList.value = [
...todoList.value,
{
id: taskId.value,
name: tempTodoName.value,
done: false
}
];
updateTaskName("");
incrementTaskId();
};

const removeTodoItem = id => {
todoList.value = todoList.value.filter(item => item.id !== id);
};

const toggleDoneStatus = id => {
todoList.value = todoList.value.map(item =>
item.id === id ? { ...item, done: !item.done } : item
);
};

return {
...
addTodoItem,
removeTodoItem,
updateTaskName,
toggleDoneStatus
};
}

export default {
setup() {
...useTodo()
}
}


完成形

はい。処理をvueインスタンスの外にまるっと切り出す事が出来ました。

viewとlogicをそれぞれ別コンポーネントに切り出して、scoped slotsで渡して…といった手間がこれだけで収まってしまっています。

import { value, computed } from "vue-function-api";

const useTodo = () => {
const todoList = value([]);

const tempTodoName = value("");

const taskId = value(0);

const itemCount = computed(() => todoList.value.length);

const updateTaskName = name => {
tempTodoName.value = name;
};

const incrementTaskId = () => {
taskId.value = taskId.value + 1;
};

const addTodoItem = () => {
todoList.value = [
...todoList.value,
{
id: taskId.value,
name: tempTodoName.value,
done: false
}
];
updateTaskName("");
incrementTaskId();
};

const removeTodoItem = id => {
todoList.value = todoList.value.filter(item => item.id !== id);
};

const toggleDoneStatus = id => {
todoList.value = todoList.value.map(item =>
item.id === id ? { ...item, done: !item.done } : item
);
};

return {
todoList,
itemCount,
tempTodoName,
addTodoItem,
removeTodoItem,
updateTaskName,
toggleDoneStatus
};
};

export default {
setup() {
return {
...useTodo()
}
}
}

useTodo() を別ファイルに切り出してコンポーネントでimportしてもよし。

useTodo() をさらに細かい関数に切り出してもよし。自由度が高まりますね。


完走した感想

意外にとっつきやすく、scoped slots に breaking change が入って大ショックだった自分には嬉しい API でした。

Todo アプリ程度では覚える事も少なく、可読性も担保されています。

react hooks と違い、更新用の関数を提供せず代入メインでやって行くのは vue らしいと感じました。

設計のベストプラクティスは掴めてませんが、vue3.0 が楽しみです


参考資料

https://github.com/vuejs/vue-function-api#readme

https://bezkoder.com/vue-function-api-example/