書いた日: 19/8/9
19/8/29追記
function api は composition api にとって変わられる可能性が高そうです。
考え方自体はほぼ同じですがapiが変わっているため、
適宜読み替えてもらう必要があります。
https://www.npmjs.com/package/vue-function-api
https://vue-composition-api-rfc.netlify.com/
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/