2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

Vue2のtodoリストのチュートリアルをVue3で作ってみた

Last updated at Posted at 2021-12-18

Vueをやることになったので、チュートリアルをやってみました。
どうせなので、Vue3で始めようと思ったのですが、Vue3のチュートリアルが見つからなかったので、「基礎から学ぶ Vue.js」にて公開されているToDoリストのチュートリアルをVue3で書いてみました。

最終的なコードはGitHubにおいてあります。

基本的には以下の手順でおこないました。

  1. vue3 + Typescript + vite で環境を構築
  2. 上記チュートリアルのコードをなるべくコピペして動作するようにする。
  3. composition APIで書き換える。
  4. Element-plusで見栄えを整える。
  5. vue-routerを入れる。

見栄えをお手軽に良くしたかったので、UIのフレームワークを入れる予定でしたが、
Vue3に対応しているElement-plusを入れてみました。
Element-plusを入れるとnpm run devでは、問題なさそうでしたが、
ビルド時にvue-router関係のエラーが出たので、
vue-routerも入れる予定だったので、入れて簡単なルーティングをつけてみました。

ファイル構成

最終版のファイル構成は、以下のような感じになっています。

src
├── App.vue
├── components
│   ├── HelloWorld.vue
│   └── ToDo.vue         //ToDoリストのメインのコンポーネント
├── libs
│   ├── AddToDo.ts       //AddTodoメソッドを別ファイルに書き出した。
│   └── local_storage.ts  //ローカルストレージとのやり取りも別ファイルに書き出した。
├── main.ts
├── types
│   └── todos.ts         //ToDoリストで使う型の宣言
└── views
    └── Home.vue         //vue-routerで使った仮のホームページ

環境構築

以下のコマンドでプロジェクトフォルダを作成しました。

npm init @vitejs/app

✔ Project name: … sample
✔ Select a framework: › vue
✔ Select a variant: › vue-ts

エディタをVSCodeを使用しましたが、Vue用の言語ライブラリはVolarを使用します。VSCodeのおすすめ通りにインストールすると、Veturを入れてしまうので、機能を切り替えておく。
(参考:Zenn: Vue3 + TypeScript + Tailwindの環境構築)

以下にざっと各ファイルの説明をしてみます。

#App.vue

ソースコードはこちら(App.vue

7-13行目のところで、ローカルストレージに初期値を登録しました。

App.vue
let todos:ATodo[] = [
  { "id": 0, "comment": "新しいToDo1", "state": 0 },
  { "id": 1, "comment": "新しいToDo2", "state": 0 }
];
todoStorage.save(todos);
todoStorage.uid = todos.length;

<template>vue-routerようにルーティングのサンプルがおいてあります。

libs/local_storage.ts

ソースコードはこちら(libs/local_storage.ts

基本的にはTodoのページにある内容をそのままコピーしましたが、他の場所でuidについてのエラーがでたので、
6行目にプロパティとして追加しました。

libs/local_storage.ts
    uid:0,

また、後述しますが、fetch()の返り値については、reactiveな変数を返すように変更しました。

libs/AddToDo.ts

ソースコードはこちら(libs/AddToDo.ts

これは、compositionAPIにする際に、コンポーネントから抜き出した関数になります。ToDo.vuesetup()から呼び出しています。
抜き出す際に以下の2つを引数として設定しました。

  • todoの配列となっているtodos
  • ToDo.vueで使用している<form>で扱うデータの型の変数form
libs/AddToDo.ts
export function doAddTodo(todos:ATodo[],form:Form){

#types/todos.ts

ソースコードはこちら(types/todos.ts

このファイルは、型の定義を記述しました。
todoの型となるAToDo型(実際のtodoリストの型はAToDoの配列AToDo[]としています。)と、<form>で使用されるデータ型です。

#components/ToDo.vue

ソースコードはこちら(components/ToDo.vue

こちらがtodoリストのメインのcomponentになります。

  • compositionAPI

compositionAPIはVue3から導入されたコンポーネントの書き方で従来よりも、一連の処理をまとまったところに記述することができるようで、setup()にまとめることができます。例えばチュートリアルにあるcomputedは以下のようになっていますが、

  computed: {

    // ★STEP12
    computedTodos: function () {
      return this.todos.filter(function (el) {
        return this.current < 0 ? true : this.current === el.state
      }, this)
    },

    // ★STEP13 作業中・完了のラベルを表示する
    labels() {
      return this.options.reduce(function (a, b) {
        return Object.assign(a, { [b.value]: b.label })
      }, {})
      // キーから見つけやすいように、次のように加工したデータを作成
      // {0: '作業中', 1: '完了', -1: 'すべて'}
    }
  },

以下のように書き換えられます。(57-62行目)

components/ToDo.vue
export default defineComponent({
    setup(_, context){
        ....
        const computedTodos = computed(()=> todos.filter((el)=>{
            return current.value < 0 ? true : current.value === el.state
        }));
        const labels = computed(():{[key:number]:string}=> options.reduce((a,b)=>{
            return Object.assign(a, {[b.value]:b.label})
        },{}));
        ....
    }
}

他にもチュートリアルのコードでは、doAdd()doChangeState()doRemove()の3つのmethodが定義されていますが、これらもsetup()の中に以下のように記述しました。(64-71行目)

components/ToDo.vue
export default defineComponent({
    setup(_, context){
        ....
        const doAdd = function() { doAddTodo(todos, form) };
        const doChangeState = function(item:ATodo) { item.state = item.state ? 0 : 1 };
        // 削除の処理
        const doRemove = function(item:ATodo) {
            let index = todos.indexOf(item)
            todos.splice(index, 1)
        };
        ....
    }
}

(doAddは、前述のlibs/AddToDo.tsファイルにdoAddTodo()関数に処理を抜き出しています。)

  • Element-plus

実務の方でUIのフレームワークも使用する予定だったので、Vue3に対応しているElement-plusを導入してみました。
以下の5種類のコンポーネントを利用しました。

  • el-radio
  • el-form
  • el-button
  • el-input
  • el-table

例えば、el-radioは以下のようなチュートリアルのコードから

    <label v-for="label in options">
    <input type="radio"
        v-model="current"
        v-bind:value="label.value">{{ label.label }}
    </label>

以下のように書き換えました。

components/ToDo.vue
    <label v-for="label in options">
        <el-radio v-model="current" v-bind:label="label.value">{{ label.label }}</el-radio>
    </label>

いくつかハマった点があるので、以下にまとめておきます。

reactiveな変数の扱い

Todoリストの内容を格納する変数をリアクティブな変数として宣言したが値の代入によってリアクティブではなくなってしまった。
具体的にはlocal_storageから値をfetchするときに発生した。以下のコードではだめで、

local_storage.ts
    fetch() {
        const todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
        todos.forEach((todo:any, index:any) => {
            todo.id = index;
        });
        todoStorage.uid = todos.length;
        return todos;
    },

以下のように、reactiveな状態として返すことでうまく動いているようです。

local_storage.ts
    fetch() {
        const jtodos = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
        jtodos.forEach((todo:any, index:any) => {
            todo.id = index;
        });
        todoStorage.uid = jtodos.length;
        const todos:ATodo[] = reactive<ATodo[]>(jtodos);
        return todos;  //リアクティブな変数が返される
    },

el-form

element-plusのel-formはv-modelに変数を設定するような使い方のようだったので、専用の型(前述のtypes/todos.tsファイルにあるForm型)を作成して使用しています。
これによりlibs/AddToDo.tsにあるdoAddTodo()関数の引数にForm型を渡しています。

types/todos.ts
export class Form{
    constructor(comment:string){
        this.comment = comment
    }
    comment:string;
}

el-table

element-plusのel-table:dataにデータをバインドして、el-table-columnprop属性でバインドしたデータのキーを指定するような使い方になっています。
少し変わった使い方をする場合は、<template>を使って、el-table-columnコンポーネントに注入する必要があるようです。(これをslotと呼ぶみたいです。)
例えば以下のように「コメント」列は、prop属性にキーを指定するだけですが、「状態」列は行番号に対応する値を引数にして関数を呼ぶボタンをつける必要があるので、scope.$indexの値を使って行の番号を使用しています。

components/ToDo.vue
        <el-table-column label="コメント" prop="comment" />
        <el-table-column label="状態">
            <template #default="scope">
                <el-button type="primary" @click="doChangeState(computedTodos[scope.$index])">
                    {{labels[computedTodos[scope.$index].state]}}
                </el-button>
            </template>
        </el-table-column>

一応動いているように見えますが、

なにか問題点がありそうなら教えて下さい。

2
1
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?