クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【Vue.js】localStorageと連携したTodoリストを読み解く(JS編-1)

Vue.jsシリーズ、公式サイトのサンプルにあったlocalStorageと連携したTodoリストのコードを読み解く。前回はHTML、CSS編でした。今回はJS編。localStorageと関わる部分を見たかったけどボリュームがあったのでまずは全体像から。それではいきましょう!

【目次】

※前回:【Vue.js】localStorageと連携したTodoリストを読み解く(HTML、CSS編) - クモのようにコツコツと

Todoリスト完成品

まずは完成品、こちらです。

See the Pen Vue de Localstarage by イイダリョウ (@i_ryo) on CodePen.

localStorageに保存されているため、リロードしてもリストが消えない!

f:id:idr_zz:20190728071409j:plain

//キー名
todos-vuejs-2.0

//値
[
  { "id":1, "title":"買い物に行く", "completed":false},
  {"id":2, "title":"本当に行く", "completed":false},
  {"id":3, "title":"絶対にいく", "completed":false}
]

localStorageについてはこちらを参照。

※参考:【JS】ローカルストレージの値をリンク先のフォームのinputタグのvalueに渡す! - クモのようにコツコツと

前回、HTMLタグの中にv-xxx:@などVue.jsの独自属性がたくさんあった。JSにそれらと紐づいた処理が書かれているはず。

JS

JSコードを見ていく。

JSコード全体

コード全体はこちら。結構ボリューミー。。

// Full spec-compliant TodoMVC with localStorage persistence
// and hash-based routing in ~120 effective lines of JavaScript.

// localStorage persistence
var STORAGE_KEY = 'todos-vuejs-2.0'
var todoStorage = {
  fetch: function () {
    var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || '[]')
    todos.forEach(function (todo, index) {
      todo.id = index
    })
    todoStorage.uid = todos.length
    return todos
  },
  save: function (todos) {
    localStorage.setItem(STORAGE_KEY, JSON.stringify(todos))
  }
}

// visibility filters
var filters = {
  all: function (todos) {
    return todos
  },
  active: function (todos) {
    return todos.filter(function (todo) {
      return !todo.completed
    })
  },
  completed: function (todos) {
    return todos.filter(function (todo) {
      return todo.completed
    })
  }
}

// app Vue instance
var app = new Vue({
  // app initial state
  data: {
    todos: todoStorage.fetch(),
    newTodo: '',
    editedTodo: null,
    visibility: 'all'
  },

  // watch todos change for localStorage persistence
  watch: {
    todos: {
      handler: function (todos) {
        todoStorage.save(todos)
      },
      deep: true
    }
  },

  // computed properties
  // http://vuejs.org/guide/computed.html
  computed: {
    filteredTodos: function () {
      return filters[this.visibility](this.todos)
    },
    remaining: function () {
      return filters.active(this.todos).length
    },
    allDone: {
      get: function () {
        return this.remaining === 0
      },
      set: function (value) {
        this.todos.forEach(function (todo) {
          todo.completed = value
        })
      }
    }
  },

  filters: {
    pluralize: function (n) {
      return n === 1 ? '件' : '件'
    }
  },

  // methods that implement data logic.
  // note there's no DOM manipulation here at all.
  methods: {
    addTodo: function () {
      var value = this.newTodo && this.newTodo.trim()
      if (!value) {
        return
      }
      this.todos.push({
        id: todoStorage.uid++,
        title: value,
        completed: false
      })
      this.newTodo = ''
    },

    removeTodo: function (todo) {
      this.todos.splice(this.todos.indexOf(todo), 1)
    },

    editTodo: function (todo) {
      this.beforeEditCache = todo.title
      this.editedTodo = todo
    },

    doneEdit: function (todo) {
      if (!this.editedTodo) {
        return
      }
      this.editedTodo = null
      todo.title = todo.title.trim()
      if (!todo.title) {
        this.removeTodo(todo)
      }
    },

    cancelEdit: function (todo) {
      this.editedTodo = null
      todo.title = this.beforeEditCache
    },

    removeCompleted: function () {
      this.todos = filters.active(this.todos)
    }
  },

  // a custom directive to wait for the DOM to be updated
  // before focusing on the input field.
  // http://vuejs.org/guide/custom-directive.html
  directives: {
    'todo-focus': function (el, binding) {
      if (binding.value) {
        el.focus()
      }
    }
  }
})

// handle routing
function onHashChange () {
  var visibility = window.location.hash.replace(/#\/?/, '')
  if (filters[visibility]) {
    app.visibility = visibility
  } else {
    window.location.hash = ''
    app.visibility = 'all'
  }
}

window.addEventListener('hashchange', onHashChange)
onHashChange()

// mount
app.$mount('.todoapp')

JS全体像

まずは一番外側の塊から見ていき、全体像をイメージしたい。処理部分を省略したのがこちら。

// Full spec-compliant TodoMVC with localStorage persistence
// and hash-based routing in ~120 effective lines of JavaScript.

// localStorage persistence
var STORAGE_KEY = 'todos-vuejs-2.0'
var todoStorage = {
  //処理
}

// visibility filters
var filters = {
  //処理
}

// app Vue instance
var app = new Vue({
  //処理
})

// handle routing
function onHashChange () {
  //処理
}

window.addEventListener('hashchange', onHashChange)
onHashChange()

// mount
app.$mount('.todoapp')

うむ、コンパクトになった♪

冒頭のコメント

コメントを和訳してみる。

localStorage持続性を備えた完全仕様準拠のTodoMVC そしてJavaScriptの120行までの有効な行でハッシュベースのルーティング。

変数STORAGE_KEYと変数todoStorage

次のコメントを和訳。

localStorageの持続性

ここら辺にlocalStorageの処理が書かれていそう。

変数filters

可視性フィルタ

可視性フィルタとはなんだろう。

変数app

app Vueインスタンス

ここでVueインスタンスが作られている。

関数onHashChange()

ルーティングを処理する

ルーティング。ハッシュチェンジ。切り替え系の処理が書かれていそう。そしてすぐ下にハッシュチェンジhashchangeのイベントリスナとonHashChange()関数実行が書かれている。

ハッシュチェンジとはページ内リンクでURLの後ろに付く#hogeなどのこと。

※参考:onhashchange - フラグメント識別子の変更時に発火する

マウント

最後にマウント。

 vm.$mount( [elementOrSelector] )

vmオブジェクトに$mount()メソッドが続く。jQueryみたいな$付きのメソッドだがVue.js独自のメソッドのよう。

Vue インスタンスがインスタンス化において el オプションを受け取らない場合は、DOM 要素は関連付けなしで、”アンマウント(マウントされていない)” 状態になります。vm.$mount() は アンマウントな Vue インスタンスのマウンティングを手動で開始するために使用することができます。

※参考:API — Vue.js

ふむ。確かに後述するVueインスタンスの中にはelオプションはない。その代わりにここで. todoappタグの紐づけているわけか。

Vueインスタンス全体像

次にVueインスタンスの全体像を見ていく。

// app Vue instance
var app = new Vue({
  // app initial state
  data: {
    //処理
  },

  // watch todos change for localStorage persistence
  watch: {
    //処理
  },

  // computed properties
  // http://vuejs.org/guide/computed.html
  computed: {
    //処理
  },

  filters: {
    //処理
  },

  // methods that implement data logic.
  // note there's no DOM manipulation here at all.
  methods: {
    //処理
  },

  // a custom directive to wait for the DOM to be updated
  // before focusing on the input field.
  // http://vuejs.org/guide/custom-directive.html
  directives: {
    //処理
  }
})

変数appでVueインスタンスを作成している。また、コメントを和訳してみる。

dataオプション

アプリ初期状態

watchオプション

localStorageの持続性のためにタスクの変更を監視する

localStorageの監視関係の処理がここに書かれていそう。

watchについてはこちらの記事も参照。

※参考:【Vue.js】watch(監視プロパティ)で戦国時代クイズを作った - クモのようにコツコツと

computedオプション

計算プロパティ

計算系の処理がかかれていそう。

コメントのリンク先はこちら。英語ページです。

※参考:Redirecting...

computedについてはこちらも参照。

※参考:【Vue.js】computed(算出プロパティ)で割り勘アプリを作る(おまけでこち亀アプリも) - クモのようにコツコツと

filtersオプション

先ほど変数filtersが出てきたので、関係していそう。

filtersオプションはあまり触れる機会がなかった。公式サイトを見てみる。

※参考:フィルター — Vue.js

methodsオプション

データロジックを実装するメソッド。  DOMの操作はまったくありません。

名前の通りメソッドの処理が書かれている。おなじみのオプション。

directivesオプション

directivesオプションって初めて見た。

DOMが更新されるのを待つカスタムディレクティブ  入力フィールドに注目する前に

DOMおん更新に対する処理が書かれているようだ。

directivesオプションは初めて見た。

公式サイトを見てみる。

※参考:カスタムディレクティブ — Vue.js

続きます…

全体像だけでもボリュームがあったので、処理の詳細については次回に見ていきます。それではまた!

※続き書きました!

www.i-ryo.com


※参考:Vue.jsの習得のためにやったことまとめ
qiita.com