初めてのVue④コードの整理

初めてのVue④コードの整理

コンポーネントを使ってわかりやすくコードが書けるようになったので、以前使ったコードをいくつかのファイルに分けて書いてみましょう。今回は複雑に思うかもしれませんが、塊で考えれば分かりやすいと思います。

この記事の対象者
  • Vueのコードをわかりやすく書きたい
  • 親と子のデータの受け渡し方を学びたい
  • コンポーネントをうまく使えるようになりたい

今回は下記のコードを整理していきます。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Document</title>
  <script src="https://unpkg.com/vue@3"></script>
</head>
<body>
 <div id="app3">
    <section v-show="inProgressAssingments.length">
      <h2>In Progress Assingments</h2>
      <ul>
        <li v-for="assingment in inProgressAssingments":key="assingment.id">
          <label>
            {{assingment.name}}
            <input type="checkbox" v-model="assingment.complete">
          </label>
        </li>
      </ul>
    </section>

    <section v-show="completeAssingments.length">
      <h2>Completed</h2>
      <ul>
        <li v-for="assingment in completeAssingments":key="assingment.id">
          <label>
            {{assingment.name}}
            <input type="checkbox" v-model="assingment.complete">
          </label>
        </li>
      </ul>
    </section>

  </div>

  <script type="module">
  let app3 = {
      data(){
        return{
          assingments: [
          {name: 'task1', complete: false, id: 1 },
          {name: 'task2', complete: false, id: 2 },
          {name: 'task3', complete: false, id: 3 },
          ]
        }
      },

      computed: {
        inProgressAssingments(){
          return this.assingments.filter(assingment => ! assingment.complete);
        },
        completeAssingments(){
          return this.assingments.filter(assingment => assingment.complete);
        }
      }
    };

    Vue.createApp(app3).mount('#app3');
  </script>
</body>
</html>

そうですね、少し前に使ったコードです。

これを下記の4つのファイルに分けていきます。

まずはindex.htmlです。

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>Document</title>
  <script src="https://unpkg.com/vue@3"></script>
</head>
<body>
  <div id="app3">  
  </div>
  <script type="module">
    import app3 from './js/components/App.js';
    Vue.createApp(app3).mount('#app3');
  </script>
</body>
</html>

app3が使えるようにしただけです。ほとんど何もなくなりますね。

続いて、App.jsからimportしているのでApp.jsを見ていきましょう。

import Assingments from "./Assingments.js";

export default {
    components: {Assingments},

    template: `
      <assingments></assingments>
    `,
}

いろんなインスタンスのハブになるイメージで作っています。

Assingmentsを使えるようにしてassingmentsタブを使っているだけです。

続いてAssingments.jsを見ていきましょう。

mport AssingmentList from "./AssingmentList.js";

export default{
  components: { AssingmentList },
//ポイント①:assingmentsにフィルターした配列、titleにタイトルを渡してます。これらは子(AssingmentList)の中で使います
  template: `
    <assingment-list :assingments="filters.inProgress" title="In Progress"></assingment-list>
    <assingment-list :assingments="filters.completed" title="Completed"></assingment-list>
    ` ,
    data(){
        return{
          assingments: [
          {name: 'task1', complete: false, id: 1 },
          {name: 'task2', complete: false, id: 2 },
          {name: 'task3', complete: false, id: 3 },
          ]
        }
      },
//ポイントではないですが、こんな書き方もできるということで書き換えました。
      computed: {
        // inProgressAssingments(){
        //   return this.assingments.filter(assingment => ! assingment.complete);
        // },

        // completeAssingments(){
        //   return this.assingments.filter(assingment => assingment.complete);
        // },

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

}

やっとコードが出てきました。

元のデータはここにあって、フィルターしたデータを子のファイルでへ送ってます。

続いて、AssingmentsList.jsを見ていきましょう。

import Assingment from "./Assingment.js";

export default{
  components:{ Assingment },

//ポイント①:親から受け取ったassingmentsやtitleを使ってテンプレートを書いてます。
  template: `
    <section v-show="assingments.length">
      <h2>{{ title }}</h2>
      <ul>

//ポイント②:assingmentsの中のそれぞれの要素は孫ファイル(Assingment)に渡してます
        <assingment
          v-for="assingment in assingments"
          :key="assingment.id"
          :assingment="assingment"
        >
        </assingment>
        
      </ul>
    </section>
  `,

//ポイント③親からのデータを受け取れるようにpropsを定義してます。
  props: {
    assingments: Array,
    title: String,
  }
}

Propでデータ型を設定しておくことを忘れないようにしましょう。

続いて、Assingment.jsを見ていきましょう。

export default {
  template: `
    <li>
      <label>
//ポイント①:受け取ったassingmentオブジェクトを使ってテンプレートを書いています
        {{assingment.name}}
        <input type="checkbox" v-model="assingment.complete">
      </label>
    </li>
  `,

//ポイント②:propsの設定も忘れずに!
  props: {
    assingment: Object
  }
}

これでまとまりごとにファイルが分けれたのでコードが書きやすくなりました。

親と子のコミュニケーション

親から子にデータを渡すときはpropsが使えるのですが、子から親の時はどうすればいいのでしょうか?

新しくAssingmentを追加するときを例に見ていきましょう。

コードを書いていきます。

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を追加
    <assingment-create :assingments="assingments"></assingment-create>
    ` ,
    data(){
        return{
          assingments: [
          {name: 'task1', complete: false, id: 1 },
          {name: 'task2', complete: false, id: 2 },
          {name: 'task3', complete: false, id: 3 },
          ]
        }
      },

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

}
export default{
  template:`
//ポイント①:submitした時にページが再読み込みされないようpreventしてます
    <form @submit.prevent="add">
      <div>
        <input v-model="newAssingment" placeholder="New assingment">
        <button type="submit">Add</button>
      </div>
    </form>
  ` ,

  props:{
    assingments: Array
  },

//ポイント②:addが発火した時に使う変数を定義してます。
  data(){
    return{
      newAssingment:''
    }
  },

//ポイント③:addが発火した時の処理を書いています。
  methods:{
    add(){
      this.assingments.push({
        name: this.newAssingment,
        complete: false,
        id: this.assingments.length +1
      });

      this.newAssingment='';
    }
  }
}

AssingCreateのインスタンスにもpropでAssingmentListの配列が渡っているのがわかると思います。

これは少し変な感じがします。

原因は子から親にpropsを使ってアクセスしているからです。

ではどうするかというと、emitを使って書き換えます。

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

  // props:{
  //   assingments: Array
  // },

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

//ポイント:emitを使ってaddを発火します、引数はnewAssingmentを使ってくださいと書きます
  methods:{
    add(){
      this.$emit('add', this.newAssingment);
      // this.assingments.push({
      //   name: this.newAssingment,
      //   complete: false,
      //   id: this.assingments.length +1
      // });

      this.newAssingment='';
    }
  }
}

親ではどうやってデータを受け取っているのでしょうか?

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>

//ポイント①:もともとassingmentsを定義してたとこにaddを定義
    <assingment-create @add="add"></assingment-create>
    ` ,
    
    data(){
        return{
          assingments: [
          {name: 'task1', complete: false, id: 1 },
          {name: 'task2', complete: false, id: 2 },
          {name: 'task3', complete: false, id: 3 },
          ]
        }
      },

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

//ポイント②:ここでassingmentsへの追加の処理を行う。nameには子から渡されたnewAssingmentが入る。
      methods: {
        add(name){
          this.assingments.push({
            name: name,
            complete: false,
            id: this.assingments.length +1
          });

          // this.newAssingment='';
        }
      }

}

子から親にアクセスするときはemit、親から子にアクセスするときはpropと覚えておきましょう。

関連記事