🥬

N予備校1章終えた人向けVue入門3

2022/12/15に公開

こちらの記事の続きです。

https://zenn.dev/dove/articles/c4073b6979de32

Step7 リストレンダリング

リンク

次は、Vueびにおけるリストの扱い方を学びます。従来であればリストといえば、次のような書き方でした。

<ul>
  <li>item1</li>
  <li>item2</li>
  <li>item3</li>
</ul>

Vueではこのような繰り返し要素の書き方が、forループのように書けます。最多のサンプルコードを見てください。

<template>
  <ul>
    <li v-for="item in items" :key="item">
      {{ item }}
    </li>
  </ul>
</template>

<script>
export default {
  data(){
    return {
      items: ['item1', 'item2', 'item3']
    }
  }
}
</script>

liタグに見慣れないv-forkeyがありますね。v-for="item in items"とは、data()で定義したitems配列をひとつずつitemに代入して、繰り返しそのタグを生成するという意味です。item<li>タグから閉じタグ</li>までの範囲で、利用できます。

keyv-forを使う場合はセットでつけなければいけません。Vueはどのタグの内容が変更されてどのタグの内容を実際にブラウザで描画しないといけないのか常に考えています。この「どの」に当たる部分が、v-forのリスト生成だとVueは識別できないみたいなので、こちら側が識別用のkeyというものを指定して上げる必要があります。keyには配列の要素固有の値を入れてあげてください。文字でも数字でも大丈夫です。

ではチュートリアルをみてみましょう。

<script>
// give each todo a unique id
let id = 0

export default {
  data() {
    return {
      newTodo: '',
      todos: [
        { id: id++, text: 'Learn HTML' },
        { id: id++, text: 'Learn JavaScript' },
        { id: id++, text: 'Learn Vue' }
      ]
    }
  },
  methods: {
    addTodo() {
      // ...
      this.newTodo = ''
    },
    removeTodo(todo) {
      // ...
    }
  }
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo">
    <button>Add Todo</button>    
  </form>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      {{ todo.text }}
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
</template>

変数newTododata()で定義されています。これをv-modelinputタグと連携させています。つまりinputタグになにかテキストが打ち込まれたらnewTodoに値が代入されるということですね。

またformタグにsubmitイベントが定義されています。formタグはformタグの中にbuttonタグをおいておくと、buttonクリック時にsubmitイベントが発火します。
https://ja.javascript.info/forms-submit

今回はこれを利用して、addTodo関数を発火させています。

このTODOリストを動くようにするためには、次のことを行います。

  1. addTodo関数が実行されると、変数todosの配列に、変数newTodoの内容を追加する。2通りの方法がある。
// 追加
this.todos.push({ id: id++, text: 'aaa' }); // このaaaを`newTodo`の内容にしたい
  1. removeTodo関数が「X」ボタンと紐付いている。この関数が実行されると該当するtodoを削除したい。次の配列の持つfilter関数を使って、これを実現する。
// 削除
this.todos = this.todos.filter(/* ... */)

配列はfilterの他にmapreduceといった関数を持ちます。これらはforループの亜種だと考えてください。N予備校1章では、forループを学びましたが、モダンjs開発は基本的にforループは使わずに、mapfilterreduceを使います。

ではチュートリアルに挑戦してみましょう!

Step7は以上です。

Step8 算出プロパティー

リンク

今回は算出プロパティーを学びます。data()を理解できていれば、難しくはありません。
次のサンプルコードを見てください。

<template>
  <div>
    <span>挨拶: {{aisatu}}</span><br>
    <span>元気な挨拶: {{aisatu}}</span>
  </div>
</template>

<script>
export default {
  data(){
    return {
      aisatu: 'こんにちは'
    }
  }
}
</script>

挨拶: こんにちは
元気な挨拶: こんにちは

が表示されますね。元気な挨拶の場合は「!」をつけたいと思います。このようにdata()で定義した変数を加工表示したいときに 算出プロパティーcomputed は役に立ちます。scriptタグ内で、computedを定義して、そのなかで関数を定義すると、templateで関数名を{{}}内部で指定できるようになります。

<template>
  <div>
    <span>挨拶: {{aisatu}}</span><br>
    <span>元気な挨拶: {{ genki }}</span>
  </div>
</template>

<script>
export default {
  data(){
    return {
      aisatu: 'こんにちは'
    }
  },
  computed: {
    genki() {
      return this.aisatu + "!";
    }
  }
}
</script>

では問題です。次のサンプルコードにコードを追加して、変数valueの2倍の値(twiceValue)が表示されるようにしてください。

<template>
  <div>
    <span>もとの値: {{ value }}</span><br>
    <span>2倍: {{ twiceValue }}</span>
  </div>
</template>

<script>
export default {
  data(){
    return {
      value: 1
    }
  },
  computed: {
    twiceValue() {
      // これを編集
      return 1;
    }
  }
}
</script>
答え
<template>
  <div>
    <span>もとの値: {{ value }}</span><br>
    <span>2倍: {{ twiceValue }}</span>
  </div>
</template>

<script>
export default {
  data(){
    return {
      value: 1
    }
  },
  computed: {
    twiceValue() {
      // これを編集
      return this.value * 2;
    }
  }
}
</script>

ではチュートリアルをみてみましょう!次のサンプルコードを眺めてください。

<script>
let id = 0

export default {
  data() {
    return {
      newTodo: '',
      hideCompleted: false,
      todos: [
        { id: id++, text: 'Learn HTML', done: true },
        { id: id++, text: 'Learn JavaScript', done: true },
        { id: id++, text: 'Learn Vue', done: false }
      ]
    }
  },
  computed: {
    // ...
  },
  methods: {
    addTodo() {
      this.todos.push({ id: id++, text: this.newTodo, done: false })
      this.newTodo = ''
    },
    removeTodo(todo) {
      this.todos = this.todos.filter((t) => t !== todo)
    }
  }
}
</script>

<template>
  <form @submit.prevent="addTodo">
    <input v-model="newTodo">
    <button>Add Todo</button>
  </form>
  <ul>
    <li v-for="todo in todos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
  <button @click="hideCompleted = !hideCompleted">
    {{ hideCompleted ? 'Show all' : 'Hide completed' }}
  </button>
</template>

<style>
.done {
  text-decoration: line-through;
}
</style>

data()に新しくhideCompletedが定義され、templateにチェックボックスと「Show All(Hide completed)」ボタンが追加された以外はStep7とほぼ同じです。もしStep7をまだ理解してなければ、戻って復習しましょう!

今回は、「Show All(Hide completed)」ボタンがクリックするたびに変数hideCompletedのtrueとfalseが入れ替わっています。

  <button @click="hideCompleted = !hideCompleted">
    {{ hideCompleted ? 'Show all' : 'Hide completed' }}
  </button>

この変数hideCompletedがtrueの場合、チェックボックスにチェックがついたtodoを画面から見えなくしたいと思います。

チェックボックスにチェックがつけるとサンプルコードではどうなるのかみていきます。以下はリスト部分を抜き出したサンプルコードです。

    <li v-for="todo in todos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>

inputタグのtypeがcheckboxになっているので、これはチェックボックスを表示してくれます。
http://www.htmq.com/html5/input.shtml

そしてv-modeltodo.doneに紐付いています。このtodov-for="todo in todos"で定義されており、配列todosの1つ1つのtodoをループで取り出して代入したものです。

data()を見てみます。todosにStep7ではなかったdoneが追加されています。

  data() {
    return {
      newTodo: '',
      hideCompleted: false,
      todos: [
        { id: id++, text: 'Learn HTML', done: true },
        { id: id++, text: 'Learn JavaScript', done: true },
        { id: id++, text: 'Learn Vue', done: false }
      ]
    }
  },

どうやら、このdonev-modelが紐付いているみたいです。チェックボックスにチェックをいれると、todoのdoneがtrueになり、チェックを外すとfalseになるみたいですね。

話を戻すと、この変数hideCompletedがtrueの場合、チェックボックスにチェックがついたtodoを画面から見えなくしたいです。これは、変数hideCompletedがtrueの場合、doneがfalseになっているtodoのみを表示させればいいことになります。

これをどう実現するかというと、そもそもの配列todosをループさせずに、「todosからdoneがfalseなtodoのみを抜き出した新たな配列(filteredTodos)」をループさせればいいのです。以下はサンプルコードのイメージ。

    <li v-for="todo in (doneがfalseなtodosの配列)" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>

この「todosからdoneがfalseなtodoのみを抜き出した新たな配列(filteredTodos)」を実現するのにうってつけなのが、算出プロパティー(computed)です。

ということで、以下のサンプルコードを完成させれば、このチュートリアルは完了です。

<script>
let id = 0

export default {
  data() {
    return {
      newTodo: '',
      hideCompleted: false,
      todos: [
        { id: id++, text: 'Learn HTML', done: true },
        { id: id++, text: 'Learn JavaScript', done: true },
        { id: id++, text: 'Learn Vue', done: false }
      ]
    }
  },
  computed: {
    filteredTodos(){
      // 以下のコードを書き換えて、doneがfalseなtodoのみの配列を返しましょう
      return this.todos;
    }
  },
  ~省略~
}
</script>

<template>
  ~省略~
  <ul>
    <li v-for="todo in filteredTodos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
  ~省略~
</template>
答え
<script>
let id = 0

export default {
  data() {
    return {
      newTodo: '',
      hideCompleted: false,
      todos: [
        { id: id++, text: 'Learn HTML', done: true },
        { id: id++, text: 'Learn JavaScript', done: true },
        { id: id++, text: 'Learn Vue', done: false }
      ]
    }
  },
  computed: {
    filteredTodos(){
      // 以下のコードを書き換えて、doneがfalseなtodoのみの配列を返しましょう
      return this.todos.filter(todo => todo.done === false);
    }
  },
  ~省略~
}
</script>

<template>
  ~省略~
  <ul>
    <li v-for="todo in filteredTodos" :key="todo.id">
      <input type="checkbox" v-model="todo.done">
      <span :class="{ done: todo.done }">{{ todo.text }}</span>
      <button @click="removeTodo(todo)">X</button>
    </li>
  </ul>
  ~省略~
</template>

Step8は以上です。

Step9 ライフサイクルとテンプレート参照

リンク

このStep9まででコンポーネント単体における基本的なよく使う機能は網羅できています。Step10は応用的なもの、Step11以降はコンポーネントの組み合わせの話です。

今回は2つの話がでてきます。コンポーネントのライフサイクルと、テンプレート参照(ref)です。まずはライフサイクルの話をします。

ライフサイクルとは生まれてから、死ぬまでのことを指します。

https://v3.ja.vuejs.org/api/options-lifecycle-hooks.html#mounted

一般的、コンポーネントが生まれた瞬間(これをマウントといいます)と死ぬ瞬間(これをアンマウントといいます)にのみ処理をしたいときがあります。最初だけ〇〇したいときは、マウント時に処理をするコードを書きます。Vueファイルのどこに書けばよいかというと、scriptタグ内で、mounted()という関数を定義すればよいです。その中のコードは、コンポーネントがマウントされた瞬間に実行されるようになります。

<script>
export default {
  data(){},
  methods: {},
  computed: {},
  mounted() {
    // コンポーネントがマウントされました。
  }
}
</script>

ライフサイクルを体験してみましょう。次のコードエディタでコンソールを開いた状態で、なにかinputにタイピングしてみましょう。mountedが1回、タイピングするたびにupdateが出力されるはずです。

つぎにrefを学びます。refは正直慣れるまで難しいです。とりあえず今回理解しなくても、また必要になったときに学び直せば良いと思います。

まず、なぜrefが必要になるかという話をします。実は今まで黙っていましたが、templateタグ内部のhtmlは本物のhtmlではありません。これがそのままブラウザに描画されるのではなく、Vueがtemplateタグを含む.vueファイルを解釈して、実際のhtmlを生成します。htmlのタグのことをdom(ドム)といったりしますが、templateタグのhtmlを 仮想dom 、実際に生成されるhtmlを 生dom と言ったりします。

https://eng-entrance.com/what-is-dom

さて、この仮想domですが、実際にどれくらいの高さや幅になるかは、描画されるまでわかりません。実際の描画サイズをもとに、なにかしたい場合は、生dom にアクセスする必要があります。従来であれば、idをつけてdocument.getElementByIdでdomを取得していましたが、Vueには専用の方法が提供されています。それがrefです(多分、reference(参照)の略)。

以下は、テキスト全体の幅と高さを取得して表示するサンプルコードです。

vueを使うからには、getElementByIdをなるべく使わないようにしましょう。フレームワークが提供する思想にそって使っていくことが、そのフレームワークを使いこなすコツです。

ではチュートリアルをやってみましょう。「hello」の文字列をid属性をつけずに、適当に書き換えてください。mounted()を使用します。

<script>
export default {

}
</script>

<template>
  <p ref="p">hello</p>
</template>

以上Step9でした。お疲れさまです。

Discussion