簡単なTodoアプリをNuxt.jsとTypeScriptとComposition APIで作ってみた

# 目的
Vue + Vuexは使ったことあるが、NuxtとTypeScriptとCompostion APIは使ったことがなかったので、勉強を兼ねて簡単なTodoアプリを作ってみる。

# 環境
||version|
|---|---|
|Vue.js|2.6.11|
|Nuxt.js||
|TypeScript||
|Vuex||


# Nuxt.js + Vuex
```vue:Todo.vue
<template>
  <ul>
    <li v-for="(todo, index) in todos" :key="todo">
      {{ todo }}
      <button @click="removeAction(index)">remove</button>
    </li>
    <li>
      <input v-model="todoInput" />
      <button @click="add">add</button>
    </li>
  </ul>
</template>
<script>
import { createNamespacedHelpers } from "vuex";
const { mapState, mapActions } = createNamespacedHelpers("todo");

export default {
  data() {
    return { todoInput: "" };
  },
  computed: {
    ...mapState({ todos: state => state.todos })
  },
  methods: {
    ...mapActions(["addAction", "removeAction"]),
    add(todo) {
      this.addAction(this.todoInput);
      this.todoInput = "";
    }
  }
};
</script>
```

```js:todo.js
export const state = () => ({
  todos: ["task1", "task2"]
});

export const mutations = {
  add(state, todo) {
    state.todos.push(todo);
  },
  remove(state, index) {
    state.todos.splice(index, 1);
  }
};

export const actions = {
  addAction({ commit }, todo) {
    commit("add", todo);
  },
  removeAction({ commit }, index) {
    commit("remove", index);
  }
};
```

## 所感
* storeのmoduleをパッケージ切るだけで実現できるので簡単

# Vue.js + Vuex
今回の簡単なTodoアプリの場合、Nuxt.jsとほぼ同じなので割愛。

# Vue.js + TypeScript + Composition API

```vue:Todo.vue
<template>
  <div class="hello">
    <ul>
      <li :key="todo" v-for="(todo, index) in todos">
        {{ todo }}
        <button @click="remove(index)">削除</button>
      </li>
    </ul>
    <input v-model="todoInput" />
    <button @click="addTodo">追加</button>
  </div>
</template>

<script lang="ts">
import Vue from "vue";
import { createComponent, ref } from "@vue/composition-api";
import { useTodo } from "@/functions/useTodo";

export default createComponent({
  setup() {
    const todoInput = ref<string>("");
    const { todos, add, remove } = useTodo();

    function addTodo() {
      // refの最新の値にアクセスするにはvalueを利用する
      add(todoInput.value);
      todoInput.value = "";
    }

    return {
      todos,
      addTodo,
      remove,
      todoInput
    };
  }
});
</script>
```

```typescript:functions/useTodo.ts
import { createComponent, reactive, computed } from "@vue/composition-api";

export function useTodo() {
  const state = reactive<{ todos: string[] }>({
    todos: ["task1", "task2"]
  });
  const todos = computed(() => state.todos);

  function add(todo: string) {
    state.todos.push(todo);
  }

  function remove(index: number) {
    state.todos.splice(index, 1);
  }

  // reactiveデータは隠ぺいしてcomputedのみ公開する
  return {
    todos,
    add,
    remove
  };
}
```
## 所感
* useをprefixにつけるのは慣習らしい
* refとreactiveの使い分けがいまいち理解できていない。single objectのまま使う場合はreactiveで、separate variablesにしたい場合はrefらしい  
https://vue-composition-api-rfc.netlify.com/#ref-vs-reactive
* data, computed, methodsなどのoptionを共通化する(templateまで共通化するとcomponentになってしまう)
* TypeScriptだとVSCodeでコード補完や型チェックなどが効くので、コーディングがすごく楽
* 同一ファイル内で業務的なまとまりごとに分割するだけでもコードの見通しは良くなりそう  
![Composition API](https://user-images.githubusercontent.com/499550/62783026-810e6180-ba89-11e9-8774-e7771c8095d6.png "Composition API")

# Vue.js + Vuex + TypeScript + Class-based
Class-basedはVue3から使われなくなるため、Composition APIを利用したほうがよい。
あくまで参考情報として記載する。

```vue:Todo.vue
<template>
  <div class="hello">
    <ul>
      <li :key="todo" v-for="(todo, index) in todos">
        {{ todo }}
        <button @click="remove(index)">削除</button>
      </li>
    </ul>
    <input v-model="todoInput" />
    <button @click="add">追加</button>
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from "vue-property-decorator";
import { todoModule } from "@/store/todo";

@Component
export default class HelloWorld extends Vue {
  todoInput: string = "";

  get todos() {
    return todoModule.todos;
  }

  add() {
    todoModule.addAction(this.todoInput);
    this.todoInput = "";
  }

  remove(index: number) {
    todoModule.removeAction(index);
  }
}
</script>
```

```typescript:store/todo.ts
import {
  Mutation,
  Action,
  VuexModule,
  Module,
  getModule
} from "vuex-module-decorators";
import store from "@/store";

@Module({ dynamic: true, store, name: "todoModule" })
class TodoModule extends VuexModule {
  todos: string[] = ["task1", "task2"];

  @Mutation
  add(todo: string) {
    this.todos.push(todo);
  }

  @Mutation
  remove(index: number) {
    this.todos.splice(index, 1);
  }

  @Action
  addAction(todo: string) {
    this.context.commit("add", todo);
  }

  @Action
  removeAction(index: number) {
    this.context.commit("remove", index);
  }
}

export const todoModule = getModule(TodoModule);
```