【ハンズオン】NuxtでTypeScriptとComposition APIを使おう！

## TypeScript とは

TypeScript はマイクロソフトによって開発され、メンテナンスされているフリーでオープンソースのプログラミング言語です。

TypeScript は JavaScript に対して、省略も可能な静的型付けとクラスベースオブジェクト指向が加わりました。

詳しくは公式サイトをどうぞ。

https://www.typescriptlang.org/

## 導入

### create-nuxt-app で TypeScript を選択

```terminal:ターミナル
% npx create-nuxt-app nuxt-typescript

create-nuxt-app v3.6.0
✨  Generating Nuxt.js project in nuxt-typescript
? Project name: nuxt-typescript
? Programming language: TypeScript
? Package manager: Npm
? UI framework: None
? Nuxt.js modules: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Linting tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? Testing framework: None
? Rendering mode: Universal (SSR / SSG)
? Deployment target: Server (Node.js hosting)
? Development tools: (Press <space> to select, <a> to toggle all, <i> to invert selection)
? What is your GitHub username? XXXXX
? Version control system: Git
```

### モジュールを追加

```terminal:ターミナル
$ npm install --save-dev @nuxt/types
```

### ファイルの作成

```terminal:ターミナル
$ touch shims-vue.d.ts
```

```typescript:shims-vue.d.ts
declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}
```

### tsconfig.jsonに追記

```json:tsconfig.json
{
  "compilerOptions": {
    "target": "ES2018",
    "module": "ESNext",
    "moduleResolution": "Node",
    "lib": ["ESNext", "ESNext.AsyncIterable", "DOM"],
    "esModuleInterop": true,
    "allowJs": true,
    "sourceMap": true,
    "strict": true,
    "noEmit": true,
    "experimentalDecorators": true,
    "baseUrl": ".",
    "paths": {
      "~/*": ["./*"],
      "@/*": ["./*"]
    },
    "types": ["@nuxt/types", "@types/node"]
  },
  "files": ["shims-vue.d.ts"],
  "include": [
    "components/**/*.ts",
    "components/**/*.vue",
    "layouts/**/*.ts",
    "layouts/**/*.vue",
    "pages/**/*.ts",
    "pages/**/*.vue"
  ],
  "exclude": ["node_modules", ".nuxt", "dist"]
}
```

これで完了です。

## Composition API とは

v2.x 系の Options API での課題は以下の通りです。
コードがコンポーネントの`this`に依存するため、
コンポーネントのオプション（`data`、`computed`、`methods`、`watch`）にコードが分割され、
可読性が著しく落ちることでした。

皆さんも公式サイトの画像のように、このように分断されてしまうことが多いのではないでしょうか？
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/838372/7d2c06fc-bc0f-273d-052f-64f4cbd6f514.png)


この問題を解決するために、生まれたのが Composition API です。

https://v3.vuejs.org/guide/composition-api-introduction.html#why-composition-api

## Vue/composition-apiの導入

### モジュールを追加

```terminal:ターミナル
% npm install --save @vue/composition-api
```

### プラグインの追加

```typescript:plugins/composition-api.ts
import Vue from 'vue';
import VueCompositionApi from '@vue/composition-api';

Vue.use(VueCompositionApi);
```

### nuxt.config.js に追加

```javascript:nuxt.config.js
export default {
  plugins: ['@/plugins/composition-api'],
};
```

## Nuxt/composition-apiの導入
### モジュールを追加

```terminal:ターミナル
% npm install @nuxtjs/composition-api --save
```

### nuxt.config.js に追加

```javascript:nuxt.config.js
buildModules: [
    '@nuxtjs/composition-api/module'
  ]
```

### nuxt.config.js に追加

```javascript:nuxt.config.js
generate: {
    interval: 2000,
  }
```

これで完了です。

## 新要素

### 【TypeScript の型推論のために】defineComponent

v2.x のときは、`Vue.extend`が必要でした。
Composition API では、defineComponent に変更されました。

```vue:pages/index.vue
<script lang="ts">
import { defineComponent } from '@vue/composition-api';

export default defineComponent({});
</script>
```

### 【v2 のオプションをひとまとめに!】setup コンポーネント（引数: props, context）

```vue:pages/index.vue
<script lang="ts">
import { defineComponent, computed } from '@vue/composition-api';

export default defineComponent({
  props: {
    times: {
      type: Number,
      default: 2,
    },
  },
  setup(props, context) {
    // 2を取得
    const times = computed(() => {
      return props.times;
    });

    // Attributes (Non-reactive object)
    console.log(context.attrs);

    // Slots (Non-reactive object)
    console.log(context.slots);

    // Emit Events (Method)
    console.log(context.emit);
  },
});
</script>
```

### 【data はリアクティブに】ref, reactive

```vue:pages/index.vue
<template>
  <div>
    {{ count }}
    {{ task }}
    {{ status }}
    {{ tasks }}
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, reactive, toRefs } from '@vue/composition-api';

type TaskType = {
  task: string;
  status: string;
  tasks: string[];
};

export default defineComponent({
  setup() {
    // ref
    const count = ref<number>(0);
    console.log(count); // { value: 0 }
    console.log(count.value) // 0

    count.value ++;
    console.log(count.value) // 1

    // reactive
    const state = reactive<TaskType>({
      task: '',
      status: '',
      tasks: []
    })

    return {
      count,
      // このようにスプレッド構文で取得するとスマート（ただこの場合、toRefsがないとリアクティブじゃなくなる）
      ...toRefs(state);
    }
  },
});
</script>
```

#### リアクティブとはなんぞや

何かの値が変更された時、依存する値も変わって欲しいというのがリアクティブです。

```typescript:index.ts
// リアクティブじゃない
let x = 1;
let y = x + 4; // 5 → 5
x = 3;

// リアクティブ
let x = 1;
let y = x + 4; // 5 → 7
x = 3;
```

このリアクティブを実現するために、**ref**と**reactive**があります。

#### 使い分けに関して

| ref            | reactive       |
| -------------- | -------------- |
| プリミティブ型 | オブジェクト型 |

ちなみに、プリミティブとは 5 つの型を指します。
・number
・string
・boolean
・null
・undefined

### methods もただの関数になりました

v2.X での書き方はこのような感じです。

```vue:pages/index.vue
<script>
export default {
  data() {
    return {
      tasks: [],
      task: '',
      status: '作業中',
    };
  },
  methods: {
    addTask() {
      this.tasks.push({
        name: this.task,
        status: this.status,
      });
    },
  },
};
</script>
```

これが Composition API ではこのように変わりました。

```vue:pages/index.vue
<template>
  <div>
    <ul>
      <li v-for="(task, index) in tasks" :key="index">
        <span v-if="task.status === true"> {{ index }} : {{ task.name }} / 作業中 </span>
        <span v-else> {{ index }} : {{ task.name }} / 完了 </span>
      </li>
    </ul>
    <input type="text" v-model="task" />
    <button @click="addTask">+</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';

type Tasks = {
  name: string
  status: boolean
};

export default defineComponent({
  setup() {
    const task = ref<string>('');
    const status = ref<boolean>(true);
    const tasks = ref<Tasks[]>([]);

    const addTask = () => {
      tasks.value.push({
        name: task.value,
        status: status.value,
      });

      task.value = '';
    };

    return {
      task,
      status,
      tasks,
      addTask,
    };
  },
});
</script>
```

### computed は、@vue/composition-api から持ってくる

```vue:pages/index.vue
<template>
  <div>
    <h2>Task: {{ doTasks }}</h2>
    <ul>
      <li v-for="(task, index) in tasks" :key="index">
        <span v-if="task.status"> {{ index }} : {{ task.name }} / 作業中 </span>
        <span v-else> {{ index }} : {{ task.name }} / 完了 </span>
      </li>
    </ul>
    <input type="text" v-model="task" />
    <button @click="addTask">+</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref, computed } from '@vue/composition-api';

type Tasks = {
  name: string;
  status: boolean;
};

export default defineComponent({
  setup() {
    const task = ref<string>('');
    const status = ref<boolean>(true);
    const tasks = ref<Tasks[]>([]);

    const addTask = () => {
      tasks.value.push({
        name: task.value,
        status: status.value,
      });

      task.value = '';
    };

    const doTasks = computed(() => {
      return tasks.value.filter(t => t.status).length;
    });

    return {
      task,
      status,
      tasks,
      addTask,
      doTasks,
    };
  },
});
</script>
```

## 【ハンズオン】実際にTodoリストを作ってみよう！

### 新規追加

```vue:pages/index.vue
<template>
  <div>
    <h1>ToDoリスト</h1>
    <br />
    <input type="radio" id="all" name="type" checked />
    <label for="all">すべて</label>
    <input type="radio" id="work" name="type" />
    <label for="work">作業中</label>
    <input type="radio" id="complete" name="type" />
    <label for="complete">完了</label>
    <br />

    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>コメント</th>
          <th>状態</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(task, index) in tasks" :key="index">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button>作業中</button></td>
          <td v-else><button>完了</button></td>
          <td><button>削除</button></td>
        </tr>
      </tbody>
    </table>
    <br />
    <h2>新規タスクの追加</h2>
    <input type="text" v-model="task" />
    <button @click="addTask()">追加</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';

type Tasks = {
  name: string;
  status: boolean;
};

export default defineComponent({
  setup() {
    // state
    const task = ref<string>('');
    const status = ref<boolean>(true); // true->作業中, false->完了
    const tasks = ref<Tasks[]>([]);

    // methods
    const addTask = (): void => {
      tasks.value.push({
        name: task.value,
        status: status.value,
      });
      task.value = '';
    };

    return {
      // state
      task,
      status,
      tasks,
      // methods
      addTask,
    };
  },
});
</script>

<style></style>
```

### 削除

```vue:pages/index.vue
<template>
  <div>
    <h1>ToDoリスト</h1>
    <br />
    <input type="radio" id="all" name="type" checked />
    <label for="all">すべて</label>
    <input type="radio" id="work" name="type" />
    <label for="work">作業中</label>
    <input type="radio" id="complete" name="type" />
    <label for="complete">完了</label>
    <br />

    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>コメント</th>
          <th>状態</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(task, index) in tasks" :key="index">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button>作業中</button></td>
          <td v-else><button>完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>
      </tbody>
    </table>
    <br />
    <h2>新規タスクの追加</h2>
    <input type="text" v-model="task" />
    <button @click="addTask()">追加</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';

type Tasks = {
  name: string;
  status: boolean;
};

export default defineComponent({
  setup() {
    // state
    const task = ref<string>('');
    const status = ref<boolean>(true); // true->作業中, false->完了
    const tasks = ref<Tasks[]>([]);

    // methods
    const addTask = (): void => {
      tasks.value.push({
        name: task.value,
        status: status.value,
      });
      task.value = '';
    };

    const deleteTask = (id: number): void => {
      tasks.value.splice(id, 1);
    };

    return {
      // state
      task,
      status,
      tasks,
      // methods
      addTask,
      deleteTask,
    };
  },
});
</script>

<style></style>
```

### タスク状態変更

```vue:pages/index.vue
<template>
  <div>
    <h1>ToDoリスト</h1>
    <br />
    <input type="radio" id="all" name="type" checked />
    <label for="all">すべて</label>
    <input type="radio" id="work" name="type" />
    <label for="work">作業中</label>
    <input type="radio" id="complete" name="type" />
    <label for="complete">完了</label>
    <br />

    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>コメント</th>
          <th>状態</th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="(task, index) in tasks" :key="index">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>
      </tbody>
    </table>
    <br />
    <h2>新規タスクの追加</h2>
    <input type="text" v-model="task" />
    <button @click="addTask()">追加</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';

type Tasks = {
  name: string;
  status: boolean;
};

export default defineComponent({
  setup() {
    // state
    const task = ref<string>('');
    const status = ref<boolean>(true); // true->作業中, false->完了
    const tasks = ref<Tasks[]>([]);

    // methods
    const addTask = (): void => {
      tasks.value.push({
        name: task.value,
        status: status.value,
      });
      task.value = '';
    };

    const deleteTask = (id: number): void => {
      tasks.value.splice(id, 1);
    };

    const updateTask = (id: number): void => {
      tasks.value[id].status = !tasks.value[id].status;
    };

    return {
      // state
      task,
      status,
      tasks,
      // methods
      addTask,
      deleteTask,
      updateTask,
    };
  },
});
</script>

<style></style>

```

### タスク表示切り替え

```vue:pages/index.vue
<template>
  <div>
    <h1>ToDoリスト</h1>
    <br />
    <input type="radio" id="all" name="type" value="すべて" v-model="radio" />
    <label for="all">すべて</label>
    <input type="radio" id="work" name="type" value="作業中" v-model="radio" />
    <label for="work">作業中</label>
    <input type="radio" id="complete" name="type" value="完了" v-model="radio" />
    <label for="complete">完了</label>
    <br />

    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>コメント</th>
          <th>状態</th>
        </tr>
      </thead>
      <tbody v-for="(task, index) in tasks" :key="index">
        <tr v-if="radio === 'すべて'">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>

        <tr v-else-if="radio === '作業中' && task.status === true">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>

        <tr v-else-if="radio === '完了' && task.status === false">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>
      </tbody>
    </table>
    <br />
    <h2>新規タスクの追加</h2>
    <input type="text" v-model="task" />
    <button @click="addTask()">追加</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';

type Tasks = {
  name: string;
  status: boolean;
};

export default defineComponent({
  setup() {
    // state
    const task = ref<string>('');
    const status = ref<boolean>(true); // true->作業中, false->完了
    const tasks = ref<Tasks[]>([]);
    const radio = ref<string>('すべて');

    // methods
    const addTask = (): void => {
      tasks.value.push({
        name: task.value,
        status: status.value,
      });
      task.value = '';
    };

    const deleteTask = (id: number): void => {
      tasks.value.splice(id, 1);
    };

    const updateTask = (id: number): void => {
      tasks.value[id].status = !tasks.value[id].status;
    };

    return {
      // state
      task,
      status,
      tasks,
      radio,
      // methods
      addTask,
      deleteTask,
      updateTask,
    };
  },
});
</script>

<style></style>
```

## コードの問題点
コンポーネント内に、「**View**と**状態管理**と**ロジック**」が混在していることです。

上記のToDoであれば、100行も満たないコードなので問題ないですが、
大規模なコードであれば可読性が下がったりメンテナンスしにくいなどの問題が発生します。
（ちなみに、上記のようなToDoの場合、v2.xの方が書きやすいですw）

それでは、コンポーネントから、「View」と「状態管理＋ロジック」に切り分けましょう！

### コンポーネントから「状態管理＋ロジック」を切り分ける
新たなフォルダを作り、そこに状態管理とロジックを定義しましょう。

```terminal:ターミナル
mkdir composables
mkdir composables/store
touch composables/store/taskLogic.ts
```

これでフォルダとファイルの作成が完了しました。
それでは、こちらのファイル内に移していきましょう！

```typescript:composables/store/taskLogic.ts
import { ref } from '@vue/composition-api';

type Tasks = {
  name: string;
  status: boolean;
};

export default function taskLogic() {
  // state
  const task = ref<string>('');
  const status = ref<boolean>(true); // true->作業中, false->完了
  const tasks = ref<Tasks[]>([]);
  const radio = ref<string>('すべて');

  // methods
  const addTask = (): void => {
    tasks.value.push({
      name: task.value,
      status: status.value,
    });
    task.value = '';
  };

  const deleteTask = (id: number): void => {
    tasks.value.splice(id, 1);
  };

  const updateTask = (id: number): void => {
    tasks.value[id].status = !tasks.value[id].status;
  };

  return {
    // state
    task,
    status,
    tasks,
    radio,
    // methods
    addTask,
    deleteTask,
    updateTask,
  };
}

```

コンポーネント側はimportして必要なものをreturnで返すだけです。

```vue:pages/index.vue
<template>
  <div>
    <h1>ToDoリスト</h1>
    <br />
    <input type="radio" id="all" name="type" value="すべて" v-model="radio" />
    <label for="all">すべて</label>
    <input type="radio" id="work" name="type" value="作業中" v-model="radio" />
    <label for="work">作業中</label>
    <input type="radio" id="complete" name="type" value="完了" v-model="radio" />
    <label for="complete">完了</label>
    <br />

    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>コメント</th>
          <th>状態</th>
        </tr>
      </thead>
      <tbody v-for="(task, index) in tasks" :key="index">
        <tr v-if="radio === 'すべて'">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>

        <tr v-else-if="radio === '作業中' && task.status === true">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>

        <tr v-else-if="radio === '完了' && task.status === false">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>
      </tbody>
    </table>
    <br />
    <h2>新規タスクの追加</h2>
    <input type="text" v-model="task" />
    <button @click="addTask()">追加</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, ref } from '@vue/composition-api';
// logic
import taskLogic from '@/composables/store/taskLogic';

export default defineComponent({
  setup() {
    const { task, status, tasks, radio, addTask, deleteTask, updateTask } = taskLogic();

    return {
      // state
      task,
      status,
      tasks,
      radio,
      // methods
      addTask,
      deleteTask,
      updateTask,
    };
  },
});
</script>

<style></style>
```

### Provide/Injectでコンポーネント内で「状態管理＋ロジック」を共有

#### ①composablesにフォルダとファイルを作成

```terminal:ターミナル
$ mkdir composables/key
$ touch composables/key/taskLogicKey.ts
```

#### ②composables/key/taskLogicKey.tsにキーを作成する

```typescript:composables/key/taskLogicKey.ts
import { InjectionKey } from '@vue/composition-api';
import { taskLogicStore } from '../store/taskLogic';

const taskLogicKey: InjectionKey<taskLogicStore> = Symbol('TaskLogicStore');

export default taskLogicKey;
```

#### ③composables/store/taskLogic.tsで型情報を追加する

```typescript:composables/store/taskLogic.ts
import { ref } from '@vue/composition-api';

type Tasks = {
  name: string;
  status: boolean;
};

export default function taskLogic() {
  // state
  const task = ref<string>('');
  const status = ref<boolean>(true); // true->作業中, false->完了
  const tasks = ref<Tasks[]>([]);
  const radio = ref<string>('すべて');

  // methods
  const addTask = (): void => {
    tasks.value.push({
      name: task.value,
      status: status.value,
    });
    task.value = '';
  };

  const deleteTask = (id: number): void => {
    tasks.value.splice(id, 1);
  };

  const updateTask = (id: number): void => {
    tasks.value[id].status = !tasks.value[id].status;
  };

  return {
    // state
    task,
    status,
    tasks,
    radio,
    // methods
    addTask,
    deleteTask,
    updateTask,
  };
}

// 型情報を追加する
export type taskLogicStore = ReturnType<typeof taskLogic>;
```

#### ④親コンポーネントでProvide(＝子コンポーネントに運ぶ)
```vue:pages/index.vue
<template>
  <div>
    <h1>ToDoリスト</h1>
    <br />
    <Radio />
    <br />
    <Table />
    <br />
    <h2>新規タスクの追加</h2>
    <Task />
  </div>
</template>

<script lang="ts">
import { defineComponent, provide } from '@vue/composition-api';
// logic
import taskLogicStore from '@/composables/store/taskLogic';
// key
import taskLogicKey from '@/composables/key/taskLogicKey';
// components
import Radio from '@/components/Radio.vue';
import Table from '@/components/Table.vue';
import Task from '@/components/Task.vue';

export default defineComponent({
  components: { Radio, Table, Task },
  setup() {
    provide(taskLogicKey, taskLogicStore());
  },
});
</script>

<style></style>
```

#### ⑤子コンポーネントでInject(=親コンポーネントから受け取る)
```vue:components/Radio.vue
<template>
  <div>
    <input type="radio" id="all" name="type" value="すべて" v-model="radio" />
    <label for="all">すべて</label>
    <input type="radio" id="work" name="type" value="作業中" v-model="radio" />
    <label for="work">作業中</label>
    <input type="radio" id="complete" name="type" value="完了" v-model="radio" />
    <label for="complete">完了</label>
  </div>
</template>

<script lang="ts">
import { defineComponent, inject } from '@vue/composition-api';
// logic
import taskLogicStore from '@/composables/store/taskLogic';
// key
import taskLogicKey from '@/composables/key/taskLogicKey';

export default defineComponent({
  setup() {
    const { radio } = inject(taskLogicKey) as taskLogicStore;

    return {
      // state
      radio,
    };
  },
});
</script>

<style></style>
```

```vue:components/Table.vue
<template>
  <div>
    <table>
      <thead>
        <tr>
          <th>ID</th>
          <th>コメント</th>
          <th>状態</th>
        </tr>
      </thead>
      <tbody v-for="(task, index) in tasks" :key="index">
        <tr v-if="radio === 'すべて'">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>

        <tr v-else-if="radio === '作業中' && task.status === true">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>

        <tr v-else-if="radio === '完了' && task.status === false">
          <td>{{ index }}</td>
          <td>{{ task.name }}</td>
          <td v-if="task.status"><button @click="updateTask(index)">作業中</button></td>
          <td v-else><button @click="updateTask(index)">完了</button></td>
          <td><button @click="deleteTask(index)">削除</button></td>
        </tr>
      </tbody>
    </table>
  </div>
</template>

<script lang="ts">
import { defineComponent, inject } from '@vue/composition-api';
// logic
import taskLogicStore from '@/composables/store/taskLogic';
// key
import taskLogicKey from '@/composables/key/taskLogicKey';

export default defineComponent({
  setup() {
    const { task, status, tasks, radio, deleteTask, updateTask } = inject(
      taskLogicKey
    ) as taskLogicStore;

    return {
      // state
      task,
      status,
      tasks,
      radio,
      // methods
      deleteTask,
      updateTask,
    };
  },
});
</script>

<style></style>
```

```vue:components/Task.vue
<template>
  <div>
    <input type="text" v-model="task" />
    <button @click="addTask()">追加</button>
  </div>
</template>

<script lang="ts">
import { defineComponent, inject } from '@vue/composition-api';
// logic
import taskLogicStore from '@/composables/store/taskLogic';
// key
import taskLogicKey from '@/composables/key/taskLogicKey';

export default defineComponent({
  setup() {
    const { task, addTask } = inject(taskLogicKey) as taskLogicStore;

    return {
      // state
      task,
      // methods
      addTask,
    };
  },
});
</script>

<style></style>
```

これで完了です！