はかせだけど博士じゃない

無職が就活しないでプログラミングとかする

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インスタンス生成時に渡すオブジェクトにcomputedfiltersというプロパティを定義していて、どちらも関数を定義している。

  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を記述する。文字の色をそれぞれredpinkと記述して確認してみる。

<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

続きはまた後で。