Vue全然知らないけどNuxt.js v2.14.4 + JavaScriptでTodoアプリを作る。

はじめに

フロントエンドは素のJavaScript(ES6)とjQueryしか使ったことがないのと、雨後の筍みたいにフレームワークが乱立していて、どれから手を付けていいのかわからず困っていたのですが、Nuxt.jsが結論のようなのでNuxt.jsを使うことにしました。
チュートリアルといえばTodoアプリということで、「Nuxt.js Todo」で検索するとたくさんサンプルが出てくるのですが、どれもまともに動かないか、Deprecatedになっている方法を使っていたり、動作確認環境すら書いていないものが多いので、やはり信じられるのは公式ドキュメントだけという結論に達しました。今回は公式ドキュメントのサンプルを参考にした上で、多少機能を追加したものを作成しました。
似たような記事は山ほどあるのですが、どの記事を読んでもだめだった人向けのメモとして残しておきます。
なお、使用する言語はJavaScriptです。

今回作成するアプリのイメージ

file

ほぼ公式ドキュメントのサンプルを写経していますが、日付と検索機能を追加しています。
file

動作確認環境

  • Ubuntu 20.04.1 LTS (GNU/Linux 4.19.104-microsoft-standard x86_64)
    • Windows 10 Pro ver 2004のWSL2上で動いてるUbuntu
    • Windows向けのNode.jsもありますが、Linux環境があるならそれを使うのが一番わかりやすいと思います。
  • Node.js v12.18.3
  • Nuxt.js v2.14.4
  • yarn 1.22.5

WSL2を使用する場合の注意事項

WSL2を使用する場合、動作させるアプリケーションを/mnt/*以下に作成すると、ビルドが終わらない・ホットリロードが効かなくなる問題が発生します。私はこれで半日つぶしました。Windowsくんさぁ…。
WSL2の実行環境の中 /home/* にアプリを作れば問題を回避できます。私はVSCodeを使いたいので、Remote Development機能を使いました。
以下参考記事です。

アプリ作成

以下のコマンドを実行してアプリを作成します。Todoアプリは全部初期設定でも動作に影響ないので、全部初期設定にしました。

$ npx create-nuxt-app sample-app

参考:Nuxt.jsを使うときに、SPA・SSR・静的化のどれがいいか迷ったら – Qiita

ディレクトリ構造の参考サイト

アプリ動作確認

$ cd sample-app
$ yarn dev

http://localhost:3000 にアクセスして、デフォルト画面が出てくればOKです。
file

また、今後の開発速度に1億倍くらい影響が出てくるため、ホットリロードが効くかどうかをこの段階で検証しておいたほうがいいです。http://localhost:3000 を開いた状態で、pages/index.vue内の適当なテキストを修正して、ブラウザの更新無しで修正した内容が反映されているかを確認してください。
反映されていなければエラー内容で検索してください。

ストアの追加

Todoの状態を管理するためのストアをモジュールモードで使用します。クラシックモードはDeprecatedなのですが、大体の解説記事がクラシックモードでコードを書いていて、Nuxt.js v2で使うと警告 Classic mode for store/ is deprecated and will be removed in Nuxt 3 が出てきます。
Vuex ストア – NuxtJS
公式ドキュメントのサンプルほぼそのままですが、サンプルが間違っているので、一部修正します。また、idは採用せずに、todoの作成日時のDateを保持するよう変更しました。

// store/todos.js
export const state = () => ({
    list: []
})

export const mutations = {
    add(state, text) {
        state.list.push({
            text,
            done: false,
            created: new Date(),
        })
    },
    remove(state, todo) { // 公式ドキュメントではtodoをオブジェクトで受け取っているが、呼び出し元がオブジェクトで渡していないので正しく動作しない。
        state.list.splice(state.list.indexOf(todo), 1)
    },
    toggle(state, todo) {
        todo.done = !todo.done
    },
}

templateの修正

作成時の日付を文字列に変換する関数と、絞り込み用の関数を追加しました。検索用のテキスト入力欄に文字が入力されると、その文字列を含むリストを返却しています。

<template>
  <section class="container">
    <ul>
      <li v-for="(todo, index) in todos" :key="index">
        <input :checked="todo.done" @change="toggle(todo)" type="checkbox" />
        <span :class="{ done: todo.done }">{{ todo.text }} {{ getStringFromDate(todo.created) }}</span>
        <button @click="removeTodo(todo)">remove</button>
      </li>
    </ul>
    <p>
      <input
        :value="todoText"
        @input="todoText = $event.target.value"
        placeholder="✨What needs to be done?"
      />
      <button @click="addTodo">add</button>
    </p>
    <p>
      <input
        :value="searchText"
        @input="searchText = $event.target.value"
        placeholder="🔍Search your todo."
      />
    </p>
  </section>
</template>

<script>
import { mapMutations } from "vuex";

export default {
  data() {
    return {
      todoText: "",
      searchText: "",
    };
  },
  computed: {
    todos() {
      if (this.searchText.length > 0) {
        return this.$store.state.todos.list.filter((item) =>
          item.text.toLowerCase().includes(this.searchText.toLowerCase())
        );
      }
      return this.$store.state.todos.list;
    },
  },
  methods: {
    addTodo() {
      this.$store.commit("todos/add", this.todoText);
      this.todoText = "";
    },
    ...mapMutations({
      toggle: "todos/toggle",
    }),
    removeTodo(todo) {
      this.$store.commit("todos/remove", todo);
    },
    getStringFromDate(date) {
      return (
        date.getFullYear() +
        "-" +
        ("00" + (date.getMonth() + 1)).slice(-2) +
        "-" +
        ("00" + date.getDate()).slice(-2) +
        " " +
        ("00" + date.getHours()).slice(-2) +
        ":" +
        ("00" + date.getMinutes()).slice(-2) +
        ":" +
        ("00" + date.getSeconds()).slice(-2)
      );
    },
  },
};
</script>

<style>
</style>

所感

最初はわけがわからずかなり戸惑ったのですが、一回書き始めてみるとかなり直感的にDOMの内容を操作できますし、素のJavaScriptで同じことをやろうとすると絶対こんなにきれいに書けないので、これは流行るのもわかるフレームワークだと思いました。久々に書いていて楽しかったです。
ソースコードはGitHubにも上げておきました。
https://github.com/retrorocket/nuxtjs-2-sample-todo-app/