初めてのVue⑤Vueをより深く理解しよう

初めてのVue⑤Vueをより深く理解しよう

今回は復習も兼ねて、次回以降で使用するコードを書いていきましょう。前回使ったコードにタグでソートする機能を作っていきます。基本的なことは前回の記事と同じです。

この記事の対象者
  • 親と子の設定をマスターしたい
  • 自分でVueを動かせるようになりたい
  • 前回の記事を深く知りたい

以前まで使っていたコードに追加していきます。

親と子を考えず、どんなコードになるか考えて続きを読んでみてください。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Document</title>
  <script src="https://unpkg.com/vue@3"></script>
  <style>

//追加
    .text-red{
      color:red;
    }
  </style>

</head>
<body>

  <div id="app3">
   
  </div>

  <script type="module">
    import app3 from './js/components/App.js';

    Vue.createApp(app3).mount('#app3');

  </script>
</body>
</html>
import Assingments from "./Assingments.js";

export default {
    components: {Assingments},

    template: `
      <assingments></assingments>
    `,
}
import AssingmentList from "./AssingmentList.js";
import AssingmentCreate from "./AssingmentCreate.js";

export default{
  components: { AssingmentList, AssingmentCreate},
  template: `
    <assingment-list :assingments="filters.inProgress" title="In Progress"></assingment-list>
    <assingment-list :assingments="filters.completed" title="Completed"></assingment-list>

    <assingment-create @add="add"></assingment-create>
    ` ,

//Tagを追加
    data(){
        return{
          assingments: [
          {name: 'task1', complete: false, id: 1, tag: 'math'},
          {name: 'task2', complete: false, id: 2, tag: 'science'},
          {name: 'task3', complete: false, id: 3, tag: 'math'},
          ]
        }
      },

      computed: {
        filters(){
          return{
            inProgress: this.assingments.filter(assingment => ! assingment.complete),
            completed: this.assingments.filter(assingment => assingment.complete)
          };
        }
      },

      methods: {
        add(name){
          this.assingments.push({
            name: name,
            complete: false,
            id: this.assingments.length +1
          });

        }
      }

}
export default{
  template:`
    <form @submit.prevent="add">
      <div>
        <input v-model="newAssingment" placeholder="New assingment">
        <button type="submit">Add</button>
      </div>
    </form>
  ` ,

  data(){
    return{
      newAssingment:''
    }
  },

  methods:{
    add(){
      this.$emit('add', this.newAssingment);
      this.newAssingment='';
    }
  }
}

今回はこのファイルが重要です。

import Assingment from "./Assingment.js";

export default{
  components:{ Assingment },

  template: `
    <section v-show="assingments.length">
      <h2>
        {{ title }}
//追加
        <span>({{ assingments.length }})</span>
      </h2>
//追加
ポイント①:クリックしたときにcurrentTagがtagになり、赤色になるよう設定してます。
      <div>
        <button 
          @click="currentTag = tag"
          v-for="tag in tags"
          :class="{
            'text-red': tag == currentTag
          }"
        >
          {{ tag }}
        </button>
      </div>
      <ul>
        <assingment
//ポイント②:filteredAssingmentsからassingmentを抽出してます
          v-for="assingment in filteredAssingments"
          :key="assingment.id"
          :assingment="assingment"
        >
        </assingment>
        
      </ul>
    </section>
  `,

  props: {
    assingments: Array,
    title: String,
  },
//追加
ポイント③:currentTagをデフォルトでallにしてます
  data(){
    return{
      currentTag:'all'
    };
  },
//追加
ポイント④:タグに応じたタスクがソートされassingmentsに入るよう設定してます
  computed: {
    filteredAssingments(){
      if(this.currentTag == 'all'){
        return this.assingments;
      }
      return this.assingments.filter(a => a.tag === this.currentTag);
    },

//ポイント⑤:tagsにはallとassingmentsの中のタグを抽出してます。
    tags(){
      return [ 'all' , ...new Set(this.assingments.map(a => a.tag))];
    }
  }
}
export default {
  template: `
    <li>
      <label>
        {{assingment.name}}
        <input type="checkbox" v-model="assingment.complete">
      </label>
    </li>
  `,

  props: {
    assingment: Object
  }
}

ほぼAssingmentsList.jsでの追加です。

では、ブラウザで確認します。

mathを選択した時の画面です。

tagがmathのタスクがソートされていていい感じです。

選択したタグを赤字にする設定はちょっと特殊な書き方をしていますが、下記でも同じです。

:class="tag == currentTag ? 'text-red' : ''"

では、次の復習です。

コードを綺麗にするため、AssingmentTag.jsに分けてみましょう。

親と子にコードを分ける

まずは自分でどんなふうにコードを書くかイメージした後に続きを読んでみてください。

import Assingment from "./Assingment.js";

//AssingmentTag追加
import AssingmentTags from "./AssingmentTags.js";

export default{
  components:{ Assingment, AssingmentTags},

  template: `
    <section v-show="assingments.length">
      <h2>
        {{ title }}
        <span>({{ assingments.length }})</span>
      </h2>
//削除
    <div>
        <button 
          @click="currentTag = tag"
          v-for="tag in tags"
          :class="{
            'text-red': tag == currentTag
          }"
        >
          {{ tag }}
        </button>
      </div>
//ポイント①:↓に変更(assingmentsで渡ってきたタグをinitial-tagsとして子に渡します。)
       changeが子から送られてきたらcurrentTagを変更して渡します。
      <assingment-tags
        :initial-tags="assingments.map(a=> a.tag)"
     :current-tag = "currentTag"
        @change="currentTag = $event"
      />
      <ul>
        <assingment
          v-for="assingment in filteredAssingments"
          :key="assingment.id"
          :assingment="assingment"
        >
        </assingment>
        
      </ul>
    </section>
  `,

  props: {
    assingments: Array,
    title: String,
  },

  data(){
    return{
      currentTag:'all'
    };
  },

  computed: {
    filteredAssingments(){
      if(this.currentTag == 'all'){
        return this.assingments;
      }
      return this.assingments.filter(a => a.tag === this.currentTag);
    },  

//削除
  tags(){
      return [ 'all' , ...new Set(this.assingments.map(a => a.tag))];
    }
  }
}

新しく作成したファイルを設定していきます。

export default{
  template:`
//AssingmentTagから移動
    <div>
      <button 
//削除
        @click="currentTag = tag"
//ポイント①:↓に変更(親に送るのでemitを使うのでしたね)
        @click="$emit('change', tag)"
        v-for="tag in tags"
        :class="{
          'text-red': tag == currentTag
        }"
      >
        {{ tag }}
      </button>
    </div>
  `,

//ポイント②:親から渡ってきたデータを受け取れるようにしてます。
  props:{
    initialTags: Array,
    currentTag: String
  },

//AssingmentListから移動
  computed:{
    tags(){
      return [ 'all' , ...new Set(this.initialTags)];
    }
  }
}

ブラウザで確認しましょう。

同じようにできてますね。

今回は今までの知識を使ってタグの追加を行いました。

復習で頭の中が整理できたらと思います。

おまけ

今回のコードをv-modelを使ってさらに簡単に書いてみます。

以前紹介したようにv-bindとv-onを合わせてv-modelが作られています。そのため、下記のように変更できます。

<assingment-tags
//ポイント:v-bindとv-onで書かれてたコードをv-modelに変更
     v-model:currentTag="currentTag"
     :initial-tags="assingments.map(a=> a.tag)"
//削除
     :current-tag = "currentTag"
     @change="currentTag = $event"
/>
<div>
    <button 
//ポイント:changeからupdate:currentTagに変更することでv-modelが稼働します
        @click="$emit('update:currentTag', tag)"
        v-for="tag in tags"
        :class="{
          'text-red': tag == currentTag
        }"
    >
        {{ tag }}
    </button>
</div>

よりコードが綺麗になりました。

関連記事