Vue.jsのチュートリアルのコードを読む
はじめに
就活とか趣味のDTMとかしてましたが飽きたので久しぶりに勉強します。
LearnCode.academyのVue.jsチュートリアル
某記事で話題になっていたので動画を見ようかと思いましたが、コードを眺めてみると今まで学んだことでだいたい読めそうだなと思ったのでGitHubのコードをクローンしてきて読んでみようと思います。分からなかったら動画見る。
#1
htmlとjsファイルがそれぞれ1つずつのシンプルな構造。htmlはVue.jsとjsファイルを読み込んでいる以外はIDを振ったdiv
要素を用意しているだけ。
<div id="app"></div>
js側ではVueインスタンスを定義している。
const app = new Vue({ el: "#app", data: { bobby: { name: "Bobby Boone", age: 25 }, john: { name: "John Boby", age: 35, } }, template: ` <div> <h2>Name: {{john.name}}</h2> <h2>Age: {{john.age}}</h2> <h2>Name: {{bobby.name}}</h2> <h2>Age: {{bobby.age}}</h2> </div> ` })
el
で用意したIDと紐付けて、data
を用意してtemplate
がレンダリングされる、みたいなコードだと思う。
#2
ファイル構成は前回と同じ。htmlの方は中身も同じ。
js側はVueインスタンス生成時に渡すオブジェクトにcomputed
とfilters
というプロパティを定義していて、どちらも関数を定義している。
computed: { johnAgeInOneYear() { return this.john.age + 1; } }, filters: { ageInOneYear(age) { return age + 1; }, fullName(value) { return `${value.last}, ${value.first}`; } },
テンプレートにも書けるがこうして関数を用意しておくと共通化できるよ、ということだと思う。
computed
の方は特定のデータに対してロジックを書いているので定数っぽく使い、filters
は汎用的に使うための関数というような感じか。
template: ` <div> <h2>Name: {{john | fullName}}</h2> <h2>Age: {{john.age | ageInOneYear}}</h2> <h2>Name: {{bobby | fullName}}</h2> <h2>Age: {{bobby.age | ageInOneYear}}</h2> </div> `
filters
を使う際には{{ john | fullName }}
のようにパイプっぽく書いて関数に渡している。computed
の方は特に渡す必要がない。
#3
ファイル構成は同じ。html側は見出しが足されているがだいたい同じ。
js側はデータにfriendsという配列が定義されている。またmethods
プロパティに年齢をインクリメント・デクリメントする関数が定義されている。テンプレートではv-for
ディレクティブを使ったループ、v-on
ディレクティブを使ったイベントハンドリングでmethods
で定義した関数の呼び出し、v-model
ディレクティブを使ったバインディング?が書かれている。
const app = new Vue({ el: "#app", data: { friends: [ { first: "Bobby", last: "Boone", age: 25 }, { first: "John", last: "Boone", age: 35, } ], }, filters: { ageInOneYear(age) { return age + 1; }, fullName(value) { return `${value.last}, ${value.first}`; } }, methods: { decrementAge(friend) { friend.age = friend.age - 1; }, incrementAge(friend) { friend.age = friend.age + 1; }, }, template: ` <div> <h2 v-for="friend in friends"> <h4>{{friend | fullName}}</h4> <h5>age: {{friend.age}}</h5> <button v-on:click="decrementAge(friend)">-</button> <button v-on:click="incrementAge(friend)">+</button> <input v-model="friend.first"/> <input v-model="friend.last"/> </h2> </div> ` })
なんかcomputed
無くても全然書けるな?という感じがするが、computed
というくらいだからキャッシュするようになっているんだろうと思うのでいつかパフォーマンスの比較をしてみたい。
#4
Vueコンポーネントを扱っているっぽい。やっと学びたかったところが出てきた。
ファイル構成は同じ。htmlは中身も同じ。
js側はさっそくVueコンポーネントが記述されている。
Vue.component('friend-component', { props: ['friend'], filters: { ageInOneYear(age) { return age + 1; }, fullName(value) { return `${value.last}, ${value.first}`; } }, methods: { decrementAge(friend) { friend.age = friend.age - 1; }, incrementAge(friend) { friend.age = friend.age + 1; }, }, template: ` <div> <h4>{{friend | fullName}}</h4> <h5>age: {{friend.age}}</h5> <button v-on:click="decrementAge(friend)">-</button> <button v-on:click="incrementAge(friend)">+</button> <input v-model="friend.first"/> <input v-model="friend.last"/> </div> ` });
パッと見Vueインスタンスの生成と同じに見える。違うのはel
がないのとprops
というのが定義されていること。
親コンポーネントからデータを受け取るためにprops
を定義している。親のテンプレートは以下のようになっている。
template: ` <div> <friend-component v-for="item in friends" v-bind:friend="item" /> </div> `
friend-component
という要素を記述している。Vue.component
の第1引数と同じ名前だ。v-for="item in friends"
でfriends
の要素数だけ子コンポーネントを生成、v-bind:friend="item"
で子で定義したfriend
とバインドするような感じか。
#5
ファイル構成README.mdが追加された以外は同じ。htmlは中身も同じ。README.mdには今回使用するAPIの仕様が記載されている。
js側は、Vueコンポーネントの記述はなくVueインスタンスの定義が書かれている。新しい部分はmethods
で定義している関数内でfetch
という関数を使っていること、mounted
という関数が定義されていること。
const app = new Vue({ el: "#app", data: { editFriend: null, friends: [], }, methods: { deleteFriend(id, i) { fetch("http://rest.learncode.academy/api/vue-5/friends/" + id, { method: "DELETE" }) .then(() => { this.friends.splice(i, 1); }) }, updateFriend(friend) { fetch("http://rest.learncode.academy/api/vue-5/friends/" + friend.id, { body: JSON.stringify(friend), method: "PUT", headers: { "Content-Type": "application/json", }, }) .then(() => { this.editFriend = null; }) } }, mounted() { fetch("http://rest.learncode.academy/api/vue-5/friends") .then(response => response.json()) .then((data) => { this.friends = data; }) }, template: ` <div> <li v-for="friend, i in friends"> <div v-if="editFriend === friend.id"> <input v-on:keyup.13="updateFriend(friend)" v-model="friend.name" /> <button v-on:click="updateFriend(friend)">save</button> </div> <div v-else> <button v-on:click="editFriend = friend.id">edit</button> <button v-on:click="deleteFriend(friend.id, i)">x</button> {{friend.name}} </div> </li> </div> `, });
mounted
はVueのライフサイクルの一つでそこにフックできる。mounted
ではインスタンスがマウントされてDOM要素にアクセスできるタイミングのようだ。ライフサイクルはほかにもたくさんあるので本格的に作る際には調べる必要がありそう。
fetch
の方はPromiseが返ってるのでよくあるAjaxのやつだと思う。(雑な理解)
#6
ファイル構成が急にしっかりしたやつ(曖昧)になった。vue-cli
を使って環境構築からやっているっぽいのでここからは動画を参照していきたい。
最初にプロジェクトを用意する。いつものvue
コマンドなので省略。
それから生成されたプロジェクトの中身を見て、HelloWorld.vue
のテンプレートから不要な要素(リンク群)をごそっと削除した。
次にHelloWorld
の子コンポーネントModule1.vue
を作成してひな型を用意する。同じものをModule2.vue
として用意する。
<template> <h1>Module1</h1> </template> <script> export default { } </script> <style> </style>
用意したら親(HelloWorld)にこれらのコンポーネントを登録する。
import Module1 from "./Module1" export default { 中略 components { Module1 } }
登録したらテンプレートに記述することでModue1を使用できる。
<template> 中略 <Module1 /> 中略 </template>
<Module1 />
を記述した場所にModule1.vue
のテンプレートh1
要素が表示される。Module2も同様にして表示できる。
次にModule1、Module2それぞれのstyle
を記述する。文字の色をそれぞれred
、pink
と記述して確認してみる。
<style> h1 { color: red } </style>
するとh1
要素の文字がすべてピンクになっていまう。Module2のstyleがModule1のスタイルをオーバーライドしてしまっている。scoped
属性を追加することでそれぞれのコンポーネントごとにスタイルが当たる期待した動作になる。
<style scoped> h1 { color: red } </style>
srcディレクトリの中身は以下の通り。
src src/App.vue src/assets src/assets/logo.png src/components src/components/HelloWorld.vue src/components/Module1.vue src/components/Module2.vue src/main.js
続きはまた後で。