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

書いた日： 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 アプリの数をおぼえているか。

``` javascript
<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` です。

``` javascript
// before

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

``` javascript
// 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 からインポートして使います。

``` javascript
// before

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

``` javascript
// after

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

  return {
    ...
    itemCount
  }
}

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

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

### methods を書き換える

methods は特にラッパーが提供されていません。
`変数名.value` プロパティを更新する至ってふつうの関数として書けます。

``` javascript
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で渡して…といった手間がこれだけで収まってしまっています。

``` javascript
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/