はじめに
最近フロントエンドの基礎をかじったので、Vue.jsを使ってアウトプットします。基礎的な文法だけで作っていきます。Vue.jsに関する解説も記述していきます。冗長な部分もありますが、ご容赦ください。
ゴール設定
基礎とはいえ、作るからにはなにか実用的なものにしたいところです。そこで、買ったけど読めていない本がたくさんあるため、それらを管理するリストを作成することにします。
今回は、テキスト入力およびラジオボタン選択によるリアルタイムフィルタ機能を実装します。
完成版
以下のgifです。
データ追加、削除、(ステータス)更新、フィルタリングの機能があります。(実際の運用を考えるとページ数表示は蛇足かもしれませんが、練習のためにつけています。)
以下ソースコードです。HTML、CSSはかなり適当です。
index.html
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>Book List</title><linkhref="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"rel="stylesheet"/><linkrel="stylesheet"href="style.css"/></head><body><divclass="container"><divid="app"><h1>Book List</h1><divclass="data"><divclass="data-filter"><divclass="data-filter-inner"><sectionclass="filter"><labelv-for="option in options":key="option.value"><inputtype="radio"v-model="current":value="option.value"/>{{ option.label }}
</label></section><sectionclass="search"><inputclass="use-icon"type="text"v-model="searchWord"placeholder=" search book title"/></section></div></div><divclass="data-add"><divclass="data-add-inner"><form@submit.prevent="addItem"><ul><li><labelfor="title">title</label><inputtype="text"v-model="title"placeholder="input book title"/></li><li><labelfor="pages">pages</label><inputtype="number"v-model.number="pages"placeholder="input total number of pages"/></li><liclass="add"><buttonid="addBtn"type="submit">add</button></li></ul></form></div></div></div><divclass="data-display"><table><thead><tr><thclass="title">title</th><thclass="pages">pages</th><thclass="state">state</th><thclass="btn">-</th></tr></thead><tbody><trv-for="book in computedBooks":key="book.id"><td>{{ book.title }}</td><td>{{ book.pages }}</td><tdclass="state"><button@click="changeState(book)">
{{ labels[book.state] }}
</button></td><tdclass="btn"><button@click="deleteItem(book)">delete</button></td></tr></tbody></table></div></div></div><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><script src="main.js"></script></body></html>
style.css
body{margin:0;padding:0;font-family:Verdana,Geneva,Tahoma,sans-serif;}.container{padding:020px;}h1{text-align:center;margin-bottom:50px;padding-bottom:10px;border-bottom:1px#dddsolid;}button{cursor:pointer;text-align:center;line-height:20px;background:rgb(236,155,155);border-radius:10px;width:80px;}button:hover{opacity:0.8;}.data{display:flex;}.data-filter{width:50%;}.data-filter-inner{margin:auto;width:300px;}.filter{margin-bottom:20px;}.searchinput{line-height:20px;width:90%;font-size:14px;font-family:FontAwesome;}input::placeholder{font-size:14px;color:#909090;}form{width:100%;}.data-add{width:50%;}.data-add-inner{margin:auto;width:100%;}.data-add-innerul{list-style:none;margin:0auto10px;text-align:center;padding:0;}.data-add-innerli{margin-bottom:5px;}.data-add-innerlilabel{width:15px;float:left;text-align:left;}.data-add-innerliinput{line-height:20px;font-size:14px;width:80%;}.data-add-innerli.add{position:relative;}button#addBtn{position:absolute;width:50px;right:7%;background:skyblue;}table{margin:50pxauto;background:white;border-collapse:collapse;width:100%;}th,td{padding:10px;text-align:center;}th{background:rgb(25,25,150);color:white;}th.title{width:50%;}th.pages{width:5%;}th.state{width:15%;}th.btn{width:10%;}tbody>tr:nth-child(odd){background:#eee;}tbody>tr:nth-child(even){background:#ddd;}td.state>button{background:rgb(118,221,135);}@media(max-width:900px){.data-add-innerliinput{width:60%;}button#addBtn{right:17%;}}
main.js
'use strict';letapp=newVue({el:'#app',data:{title:'',pages:'',books:[],options:[{value:-1,label:'all'},{value:0,label:'new'},{value:1,label:'reading'},{value:2,label:'completed'}],current:-1,searchWord:'',},watch:{books:{handler:function(){localStorage.setItem('books',JSON.stringify(this.books));},deep:true}},mounted:function(){this.books=JSON.parse(localStorage.getItem('books'))||[];},methods:{addItem:function(){if(!this.tilte&&!this.pages){return}letitem={id:this.getNewId(this.books),title:this.title,pages:this.pages,state:0};this.books.push(item)this.title=''this.pages=''},changeState:function(book){if(book.state===0){book.state=1}elseif(book.state===1){book.state=2}elseif(book.state===2){book.state=0}},deleteItem:function(book){if(confirm('Remove this data?')){letindex=this.books.indexOf(book)this.books.splice(index,1);}},getNewId:function(books){constids=books.map((element)=>element.id);return!books.length?1:Math.max(...ids)+1;}},computed:{computedBooks:function(){returnthis.books.filter(function(book){return(book.title.indexOf(this.searchWord)!==-1)&&(this.current<0?true:this.current===book.state)},this);},labels:function(){returnthis.options.reduce(function(a,b){returnObject.assign(a,{[b.value]:b.label})},{})}}})
※GitHubはこちら
前提
Vue.js : 2.6.11 (CDN)
ブラウザ:Google Chrome 80.0.3987.163
コード内容
・Vue.jsの読み込み
今回はVue.jsのCDN版を利用します。
Vue.jsを利用するために、HTMLでVue.jsを読み込みます。
<!DOCTYPE html><htmllang="ja"><head><metacharset="UTF-8"/><metaname="viewport"content="width=device-width, initial-scale=1.0"/><title>Book List</title><linkhref="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css"rel="stylesheet"/><linkrel="stylesheet"href="style.css"/></head><body><!-- ここにコードを書いていく --><script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script><!-- ここでVue.jsを読み込み --><script src="main.js"></script></body></html>
今回はこちらの雛形に沿ってHTMLを記述していきます。今後のHTMLのソースコードはこのbodyの中に記述している想定で、この雛形は省略して記述していきます。
Vue.jsのCDN版を利用する場合、HTMLのscriptタグの中で読み込みます。
公式ガイドに記述されているコードをコピペするだけで大丈夫です。今回は開発バージョンを利用します。
・データ追加
まずはデータ追加の部分です。以下HTMLです。
<divid="app"><form@submit.prevent="addItem"><ul><li><labelfor="title">title</label><inputtype="text"v-model="title"placeholder="input book title"/></li><li><labelfor="pages">pages</label><inputtype="number"v-model.number="pages"placeholder="input total number of pages"/></li><liclass="add"><buttonid="addBtn"type="submit">add</button></li></ul></form></div>
Vue.jsでは、HTMLのタグの中にv-からはじまるディレクティブという属性を記述します。このディレクティブを用いることで、DOMと、JavaScriptのコードに記述されたデータを簡単に結びつけることができます。この、DOMとデータを結びつけることをデータバインディングといいます。
上記のコードの中でVue.jsのディレクティブが使用されているのは、
・formタグの @submit.prevent
・inputタグの v-model、v-model.number
の3箇所です。
@とは、v-onというディレクティブの省略記法です。v-onを使うとイベントの発生時にメソッドを呼び出します。@を用いずにv-onを省略しないで記述すると、以下のようになります。
<formv-on:submit.prevent="addItem">
こう記述することで、formがsubmitされた際、addItem
というメソッドが呼び出されます(このaddItem
メソッドは、後述するJavaScriptのコードで定義します)。
またsubmitは、action属性で動作として画面遷移する想定ですが、preventがついていると画面遷移しません。つまり、formがsubmitされた際、画面遷移せずにaddItem
というメソッドが呼び出されます。
formタグの中でtype属性にsubmitを持つのはbuttonなので、
buttonが押下される → formがsubmitされる → 画面遷移せずにaddItem
が呼ばれる
という処理になります。
また、formをJavaScriptで定義したデータと結びつける(バインドする、と言います)には、v-modelというディレクティブを用います。
<form><inputtype="text"v-model="title"><inputtype="number"v-model.number="pages"></form>
上記のようにv-modelディレクティブを用いて記述することで、そのformがJavaScriptで定義されるtitle
およびpages
というデータとバインドされます(このtitle
、およびpages
は、JavaScriptのコードで定義します)。数値を用いたい場合は、type属性をnumberにし、v-modelに.numberという修飾子をつけます。
v-modelを用いてデータバインドした状態で、ブラウザから値を入力すると、JavaScriptで定義しているデータの値がブラウザで入力した値で更新されます。
こちらはテキストボックスのイメージ図です。今回だと、ブラウザに表示されたtitle
とpages
のテキストボックスに入力した値が、JavaScriptで定義されるtitle
、pages
というデータの値となります。
では、JavaScriptのコードを見てみます。
letapp=newVue({el:'#app',data:{title:'',pages:'',books:[],},methods:{addItem:function(){if(!this.tilte&&!this.pages){return}letitem={title:this.title,pages:this.pages,state:0};this.books.push(item)this.title=''this.pages=''},}})
まず、Vueアプリケーションは、Vue関数でVueインスタンスを作成することによって起動されます。このように1行目でインスタンスを作成し、それ以降にオプションを記述していきます。
letapp=newVue({// 以下省略
elはマウントする要素を記載します。以下のように記述すると、id=app
となるタグをマウントします。今回id=app
としているのは、HTMLに記述している一番外側のdivタグです。
el:'#app',
dataには、アプリケーションで使うデータを定義します。先ほどHTMLで出てきたtitle
とpages
を空文字で定義しておきます。先ほども記述した通り、ブラウザに表示されるテキストボックスに値を入力することで、この空文字がその値に更新されます。
また次のmethodsで使うbooks
という配列も定義します。
data:{title:'',pages:'',books:[],},
methodsでは、アプリケーションで使うメソッドを定義します。ここでは先ほどHTMLで呼び出したaddItem
というメソッドを定義します。thisをつけると先ほどのdataプロパティで定義したデータを参照できます。
methods:{addItem:function(){if(!this.tilte&&!this.pages){// titleまたはpagesが空欄の際、メソッドを終了return}letitem={title:this.title,// thisをつけることでdataを参照pages:this.pages,state:0};this.books.push(item)this.title=''// ブラウザ上のテキストボックスを空文字に戻すthis.pages=''},}
addItem
では、item
というオブジェクトをbooks
配列にpushしています。title
とpages
は、先ほどHTMLの中でv-modelディレクティブによりformとバインドされています。
つまり、addItem
では、画面上のformで入力した値(title
、pages
)が、item
オブジェクトの値になり、books
配列にpushされるという処理がなされます。もしtitle
とpages
に値が入っていなかった場合、returnでメソッドを終了させています。また、ステータスとしてitem
オブジェクトにはstate
というプロパティも持たせます。
またaddItem
の最後に、
this.title=''this.pages=''
という処理を入れています。これにより、データのtitle
と、pages
が空文字に戻ります。このtitle
とpages
はv-bindによってformとバインドされているので、すごいことに、この処理を行うことでブラウザ上のテキストボックスも空文字に戻ります。このように、v-modelでバインドした場合、ブラウザ上からもスクリプト上からもデータを更新することができます。これを双方向データバインディングといい、Vue.jsの大きな強みの一つです。この双方向データバインディングについては、また後ほど記述します。
ここまでのデータ追加の処理を振り返ると、
ブラウザ上のテキストボックスに入力した値が、JavaScriptのデータであるtitle
, pages
の値になる
→ ブラウザ上のadd
ボタンを押下すると、JavaScriptで定義したaddItem
メソッドが呼ばれる
→ データで定義されたbooks
配列にitem
オブジェクト(プロパティとしてtitle
, pages
, state
を持つ)がpushされる
→ データのtitle
, pages
が空文字になり、テキストボックスの中身も空文字になる
となります。
・データ参照、削除
次にデータを参照するtableです。tableの中に、ステータスを更新するボタン、データを削除するボタンを実装します。
以下HTMLです。
<divid="app"><!-- 省略 --><table><thead><tr><thclass="title">title</th><thclass="pages">pages</th><thclass="state">state</th><thclass="btn">-</th></tr></thead><tbody><trv-for="book in books"><td>{{ book.title }}</td><td>{{ book.pages }}</td><tdclass="state"><button@click="changeState(book)">
{{ labels[book.state] }}
</button></td><tdclass="btn"><button@click="deleteItem(book)">delete</button></td></tr></tbody></table></div>
ここでは、繰り返し処理のv-forというディレクティブが登場しています。books
から一つずつ要素を取り出し、book
という変数に代入して処理を行なっています。forの処理自体はJavaScriptと共通しているのでイメージしやすいです。
<trv-for="book in books">
また、v-onの省略記法である@も登場しています。以下ではbuttonのclick時にdeleteItem
というメソッドが呼ばれます。上で登場したbook
を引数として与えています。
<button@click="deleteItem(book)">delete</button>
なお、Vue.jsで扱うデータは、HTML側で以下のように{{ }}で囲むことで参照できます。
<td>{{ book.title }}</td>
この記法を「Mustache(マスタッシュ)」と呼びます。
tdタグでは、先ほどbooks
配列に追加したtitle
、pages
、state
を参照し、ブラウザ上に表示しています。
以下では、ステータスを表示しています。ここで登場するlabels
は後ほどJavaScriptで定義します。
{{ labels[book.state] }}
データ追加から参照までを要約すると、
formにデータを入力 → add
ボタン押下 → books
配列にデータがpush → tableに表示
という処理となっています。以下にCSS記述後のイメージを載せておきます。
※以下formイメージ図(再掲)
※以下tableイメージ図
では、上記HTMLにて記述した、state
ボタンを押すと呼ばれるメソッドであるchangeState
と、delete
ボタンを押すと呼ばれるメソッドであるdeleteItem
を、以下のJavaScriptで定義していきます。
letapp=newVue({el:'#app',data:{title:'',pages:'',books:[],options:[{value:-1,label:'all'},{value:0,label:'new'},{value:1,label:'reading'},{value:2,label:'completed'}],},methods:{addItem:function(){// 省略changeState:function(book){if(book.state===0){book.state=1}elseif(book.state===1){book.state=2}elseif(book.state===2){book.state=0}},deleteItem:function(book){if(confirm('Remove this data?')){letindex=this.books.indexOf(book)this.books.splice(index,1);}},},computed:{labels:function(){returnthis.options.reduce(function(a,b){returnObject.assign(a,{[b.value]:b.label})},{})}}})
メソッドから見ていきます。changeState
では、state
が持つ数値を変更する処理が行われます。この数値に対応するステータスは、データのoptions
で定義しています(このstate
の数値をoptions
のlabel
に変換する処理は、後ほど記述するcomputedの中で行なっています)。
changeState:function(book){if(book.state===0){book.state=1}elseif(book.state===1){book.state=2}elseif(book.state===2){book.state=0}},
deleteItem
では、books
から該当のindexのデータ (book
)を削除します。
deleteItem:function(book){if(confirm('Remove this data?')){// 削除前に確認 trueの場合のみ削除letindex=this.books.indexOf(book)this.books.splice(index,1);}},
次に、computedについてです。computedは、関数によって算出されたデータのことで、dataと似たように扱うことができます。computed内のデータを、算出プロパティと言います。
computed:{labels:function(){returnthis.options.reduce(function(a,b){returnObject.assign(a,{[b.value]:b.label})},{})}}
ここでは、labels
という算出プロパティが定義されています。これは先ほど、HTMLで登場した、ステータスを表示するボタンで呼びだされていたlabels
のことです。
ちなみに、labels
の中ではreduceを使っています。説明はJavaScriptの話になるので省略しますが、処理としてはHTML側で
{{ labels[book.state] }}
として呼び出すことで、state
の数値を、データのoptions
で定義しているlabel
に変換して表示します。
・key属性
もう一度tableのtbodyのHTMLをみてみます。
<tbody><trv-for="book in books"><!-- key属性が必要 -->>
<td>{{ book.title }}</td><td>{{ book.pages }}</td><tdclass="state"><button@click="changeState(book)">
{{ labels[book.state] }}
</button></td><tdclass="btn"><button@click="deleteItem(book)">delete</button></td></tr></tbody>
実は、v-forディレクティブを用いた時は、一意な値であるkey属性を持たせる必要がある、とスタイルガイドに明言されています。
なので以下のようにkeyにid
を持たせておきます。このid
は後ほどJavaScriptで定義します。
<trv-for="book in books":key="book.id">
ここで登場する:keyはv-bindディレクティブの省略記法を用いています。省略しないで記述すると
<trv-for="book in books"v-bind:key="book.id">
となります。v-bindについては後述しますが、HTMLの属性とJavaScriptのデータをバインドするときに用います。今回は、trタグのkey属性とbook.id
というデータをバインドしています。
では、JavaScript側でどうすれば良いかみていきます。
letapp=newVue({// 省略methods:{addItem:function(){if(!this.tilte&&!this.pages){return}letitem={id:this.getNewId(this.books),//追加title:this.title,pages:this.pages,state:0};this.books.push(item)this.title=''this.pages=''},getNewId:function(books){//追加constids=books.map((element)=>element.id);returnbooks.length==0?1:Math.max(...ids)+1;}},// 省略
まずは、item
にid
プロパティを持たせます。id
は一意な値にしたいため、「books
配列の要素が持つid
の最大値に1を加算した値」とします。これでid
が重複することはないはずです。
このid
を取得するため、getNewId
というメソッドを定義します(こちらの記事を参照させていただいています。三項演算子を用いて、books
に要素が一つもない場合に0を返す処理を入れています)。定義したgetNewId
を、id
プロパティを定義する際にthisを用いて呼び出します。引数にはbooks
配列を渡します。これで、id
プロパティを追加することができました。
上記でid
が正しく追加されているかどうかは、 tableにid
のカラムを追加して出力してみるとわかります。(実際のコードでは、idは不要なので消しています。)
<table><thead><tr><thclass="id">id</th><!-- 追加 --><thclass="title">title</th><thclass="pages">pages</th><thclass="state">state</th><thclass="btn">-</th></tr></thead><tbody><trv-for="book in computedBooks":key="book.id"><td>{{ book.id }}</td><!-- 追加 --><td>{{ book.title }}</td><td>{{ book.pages }}</td><tdclass="state"><button@click="changeState(book)">
{{ labels[book.state] }}
</button></td><tdclass="btn"><button@click="deleteItem(book)">delete</button></td></tr></tbody></table>
・フィルタリング
いよいよ、テキスト入力およびラジオボタン選択によるリアルタイムのフィルタ機能をつけていきます。
以下HTMLです。まずはテキスト入力によるフィルタリングです。
<!-- テキスト入力によるフィルタリング --><inputtype="text"v-model="searchWord"placeholder="search book title"/>
inputはテキストボックス(検索ボックス)です。ここに入力された値をsearchWord
というデータとバインドして検索できるようにします。searchWord
は後ほどJavaScriptで定義します。
CSSで装飾すると、以下のようなイメージです。
続いて、ラジオボタンによるフィルタリングです。
<!-- ラジオボタンによるフィルタリング --><labelv-for="option in options":key="option.value"><inputtype="radio"v-model="current":value="option.value"/>{{ option.label }}
</label>
こちらのinputはラジオボタンです。先に{{ option.label }}について解説します。label
タグでv-forが用いられており、{{ option.label }}ではoptions
配列の要素が持つlabel
プロパティを取り出して表示しています。keyには一意な値であるoption.value
を与えています。念のため、以下にoptions
を再掲します。
options:[{value:-1,label:'all'},{value:0,label:'new'},{value:1,label:'reading'},{value:2,label:'completed'}],
ラジオボタンのイメージは以下のようになります。
inputタグの中の:valueは、先ほども紹介した通りv-bindディレクティブの省略記法を用いています。v-bindとは、タグの中の属性に対してバインドを行いたいときに使います。この場合、inputタグのvalue属性とoption.value
をバインドしています。
<inputtype="radio"v-model="current"value="{{ option.value }}"/><!-- これはエラーになる -->
上記のように、属性に対してMustache記法を用いるとエラーになります。なので、
<inputtype="radio"v-model="current"v-bind:value="option.value"/>
とする必要があります。コードでは、v-bindの部分を省略して以下のような記述としています。
<inputtype="radio"v-model="current":value="option.value"/>
v-modelとv-bindが両方出てきてややこしいですが、双方向データバインディングを行えるのはv-modelのほうです。データ追加の章でも解説しましたが、双方向データバインディングとは、スクリプト⇄ブラウザの両方向から値の更新ができる仕組みです。この双方向データバインディングを簡単に実装できるのがVue.jsの強みです。v-bindはv-modelとは違い、スクリプト→ブラウザの単方向のデータバインディングとなります。
では、今回のコードの解説をします。今回は、v-bindにより、inputタグのvalue属性とoption.value
がバインドされています。これにより、inputタグ(ラジオボタン)のvalue属性は、-1〜2の数値になります。
そして、そのvalueが、v-modelでバインドされているcurrent
というデータの値になります。つまり、選択したラジオボタンによって、 current
の値は-1〜2のどれかに変化します。この「ブラウザで選択したラジオボタンのvalueが、スクリプトで定義されているcurrent
の値になること」がブラウザ→スクリプトのバインドです。
また、ラジオボタンを何も選択していない場合、初期値としてスクリプト側でcurrent
を定義した際の値が入っています。つまり、JavaScriptのdataで
current:'-1',
と定義しておくと、初期値は-1となり、ラジオボタンのall
が選択された状態になっています。これがスクリプト→ブラウザのバインドです。このように、v-modelを用いることで双方向のデータバインドが可能になります。
ちなみに、ラジオボタンにv-modelとv-bindを用いるという使い方は公式で紹介されています。
上記で解説したv-modelとv-bindの違いを簡単にまとめておきます。
特徴 | v-model | v-bind |
---|---|---|
バインド対象 | form, input等 | 属性 |
バインド方向 | 双方向(スクリプト⇄ブラウザ) | 単方向(スクリプト→ブラウザ) |
それでは、以下JavaScriptです。ここでは、テキスト入力やラジオボタン選択によるフィルタ処理を実装します。
letapp=newVue({el:'#app',data:{// 省略 current:-1,searchWord:'',},// 省略computed:{computedBooks:function(){returnthis.books.filter(function(book){return(book.title.indexOf(this.searchWord)!==-1)&&(this.current<0?true:this.current===book.state)},this);},// 省略}})
まずはデータで先ほど出てきたcurrent
とsearchWord
を定義しておきます。current
の初期値は-1とします。
computedでは、computedBooks
を定義します。ここでは、books
配列の中から、title
の中にsearchWord
(=テキストボックスに入力された値)を持ち、かつstate
の値がcurrent
(=ラジオボタンで選択された値)と等しいデータのみにフィルタリングするという処理を行なっています。
先ほどのHTML上のtableで記述したBooksを、このcomputedBooksに置き換えます。
<tbody><trv-for="book in computedBooks"key="book.id"><td>{{ book.title }}</td><td>{{ book.pages }}</td><tdclass="state"><button@click="changeState(book)">
{{ labels[book.state] }}
</button></td><tdclass="btn"><button@click="deleteItem(book)">delete</button></td></tr></tbody>
これでフィルタ機能が実装されました。
・ローカルストレージへの保存
現在のままだと、画面の更新を行うとデータがすべて消えてしまいます。そこで最後に、ローカルストレージにデータを保存する処理、そして保存したデータを呼び出す処理を行います。
これらの処理は、JavaScript内で行います。
letapp=newVue({el:'#app',// 省略watch:{books:{handler:function(){localStorage.setItem('books',JSON.stringify(this.books));},deep:true}},mounted:function(){this.books=JSON.parse(localStorage.getItem('books'))||[];},// 省略
watchはウォッチャと呼ばれ、特定のデータや算出プロパティの状態を監視して、変化があったときに実行させたい処理を記述します。
ここでは、books
に変化があったとき、JSON形式でローカルストレージに保存する処理を行なっています。deep
プロパティをtrueにすると、ネストされたオブジェクトも監視してくれます。
mountedでは、ローカルストレージからデータの呼び出しを行なっています。このmountedはライフサイクルフックと呼ばれているメソッドの一つです。ライフサイクルフックは、Vueのインスタンスが生成された後に呼ばれます。呼ばれるタイミングはそのメソッドによって異なり、例えばmountedは、インスタンスがマウントされた後に呼ばれます。今回は、インスタンスのマウントがされた後に、ローカルストレージからデータの呼び出しがなされています。
(ライフサイクルフックはJavaScriptのフレームワークに触れていないと理解するのが難しいです。最初はよく使うmountedとcreatedの区別からできるようになると良いかもしれません。公式のガイドはこちらです。)
これらの処理によって、画面遷移が行われても、データが消えずに残り続けます。
あとはCSS用に、記述を加えてあげると完成です。
おわりに
書いた後に気付きましたが、ソート機能も付ければよかったなあと思います。
Vue.jsはちょっと動画見た程度なので、まだまだ勉強が必要ですが、記事としてアウトプットすることで理解が深まった気がします。もう少し使えるようになったら、今後はPythonのフレームワークであるdjangoと組み合わせてなにか作っていきたいと考えています。
参考
Vue.js 公式ガイド
基礎から学ぶVue.js チュートリアル
追加と削除が繰り返される配列要素のオブジェクトに一意のid番号を振る
◆以下gifでテストデータとして使わせていただいた書籍
自宅ではじめるDocker入門―人気のコンテナ型「仮想化ソフト」を使ってみる!
基礎から学ぶVue.js
体系的に学ぶ安全なWebアプリケーションの作り方 脆弱性が生まれる原理と対策の実践
独学プログラマー Python言語の基本から仕事のやり方まで
自走プログラマー ~Pythonの先輩が教えるプロジェクト開発のベストプラクティス120