【Vue.js3入門】Composition APIのザックリした使い方解説

フロントエンドのフレームワークの一つであるReact.jsに続き、Vue.jsの学習を初めました。HTML+JavaScriptの肥大化したファイルを作成するのでなく、再利用可能なようにコンポーネントに切り分けて開発します。各コンポーネントには変数を保有できますので、コンポーネント単位のオブジェクト指向と同じです。
ここではVue.js 3から使えるようになった"Composition API"の使い方を忘れないように、大まかな図を入れたメモ書きです。

開発環境

  • OS: Ubuntu22.04
  • node : v22.13.1 / npm : v11.3.0
  • Vue.js : v 3.5.13
プロジェクトの作成

コマンドは他にもありますが、以下は簡単な構成から始められます。フレームワークはVue.jsを選択してください。

npm create vite@latest
フォルダ構成

Viteで立ち上げた初期のフォルダ構成

├──...
├──src/ 
│   ├── assets/ 
│   ├── components/ 
│       ├──HelloWorld.vue   # <-- 今回不要なので削除します
│       ├──Todos.vue          # <-- Child Component 新規追加
│   ├── App.vue          # <-- Parent Componentとして修正する
│   ├── main.js
│   ├── style.css
├── index.html  
└── ...  

開発の基本的な流れ

一つの画面の主要なロジックとテンプレートを親コンポーネントとして記述し(※ここではApp.vueをその目的で使う)、再利用可能なUI要素やロジックを切り出して、componentsフォルダに子コンポーネント(Todos.vue)として作成していきます。

App.vueにデータやメソッドを記述し子コンポーネントにデータを贈ります。受け取った子コンポーネントはそのデータを展開します。

親から子コンポーネントへのデータ渡し方

propsという特別な変数を使って子側で事前定義します。親側はこれを子テンプレートタグの中でその変数を介してデータを渡します。

親子間のデータ引き渡し説明図

子コンポーネントで定義した配列itemsは、親コンポーネントでは:itemsとして使えるになる。親側で管理する配列データtodoItemsを:items="todoItems"として渡します。

親コンポーネント

子コンポーネント


    親コンポーネント

リアクティブな変数を定義します。

const count = ref(0);
は初期値0の変数で、Refオブジェクトのvalueプロパティに格納されます。
従って、取り出す時は
count.valueで取得できます。配列やオブジェクトは reactive()で定義します。

データの登録

このままでは配列は空のままなので、追加・削除するなどのメソッドを記述します。

親コンポーネントでメソッド定義説明図

v-modelによる双方向バインディング

これはReact.jsにはない機能で、とても簡単・便利です!! v-modelを使います。

  1. タグ内で<input type="text" v-model="newtitle" />と書くだけで const newtitle = ref(''); と宣言した変数と紐付きます。view側で加えた変更は、自動的にnewtitleに反映されます。
  2. また子コンポーネントでも親からもらったデータと紐付ければ、自動でデータが反映されます。特別親側でイベントハンドラ更新処理を書かなくても良いのです。

2)についてもう少し説明を加えますと、子側では親側から受けた配列を1個ずつ取り出して表示させており、その中でチェックボックスタグのchecked属性と完了フラグ(todo.isDone)の有無と紐付けておくだけでOKです。<input type="checkbox" v-model="todo.isDone" > とすれば、チェックボックスをクリックしてON/OFFを切り替えると、その変化に応じて、完了フラグのON/OFFのデータも自動で更新されます。

<li v-for="todo in items" :key="todo.id">
      <input type="checkbox" v-model="todo.isDone">
       <span v-bind:class="{done : todo.isDone}">{{todo.title}}</span>
</li>

v-bindによる紐付け

v-bind:classやv-bind:styleで良く見かけられます。タグの属性を動的に設定するもので、spanタグを例にすると、<span v-bind:class="{done:todo.isDone}">class属性をtodo.isDoneのtrue/falseの値と紐付けています。

ここで、ダブルクオーテーション" "の中でさらに中括弧{ }で囲まれているのは、javascriptのオブジェクトリテラルを返すためです。todo.isDoneがtrueならdoneクラスが付き、falseならdoneクラスが消えますので、後はcssでdoneクラスの有無で、取り消し線を引いたり、消したりなどの装飾を付けることができます。

子コンポーネントからのイベント発火

親コンポーネントでデータを管理しているので、親側でデータを追加したり、削除します。子コンポーネントは親から配列データを受取りますが、子側で削除することはできません。

そこで、子コンポーネントからイベントを発火させて、親側はそのイベントを検知して削除するようにします。

子コンポーネントからのイベント発火させる説明図

全体の実装例

全体の実装例

App.vue

<template>
  <h2>TodoApp @Vue.js v3.5.13</h2>

  <Todos :items="todoItems" @deleteitem="deleteTodoItem" />
  <div class="center">
    <form @submit.prevent="setitem()">
        <div><input type="text" v-model="newtitle" ></div>
        <div><input type="submit" class="submit-button" value="登録"></div>
    </form>
  </div>

</template>

<script>
import Todos from './components/Todos.vue';
import  {reactive, ref}  from 'vue';

export default {
  name: 'App',
  components :{
     Todos
  },
  setup () {
     const count = ref(0);
     const newtitle = ref('');
     const todoItems = reactive([]);
    
     /**登録 */
    const setitem = () =>{
      if(newtitle.value.length <5){
          // console.log(newtitle.value.length)
          if(!newtitle.value){
              alert('Todoアイテムが入っていません')
          }else{
              alert('アイテムの文字数は5文字以上必要です')
          }
          newtitle.value=''
          return
      }

      count.value ++;
      let addItem = {
        id : count.value,
        title : newtitle.value,
        isDone : false
      };

      todoItems.push(addItem);
      newtitle.value = '';

    }

    /**削除 */
    const deleteTodoItem = (itemIdToDelete)=>{
        if(!confirm('削除しますか')){
            return
        }
        const indexToDelete = todoItems.findIndex(item => item.id === itemIdToDelete)
        if (indexToDelete !== -1) {
            todoItems.splice(indexToDelete, 1);
        }
    }

    return{
      count,
      newtitle,
      todoItems,
      setitem,
      deleteTodoItem
    };

  }

}
</script>

Todos.vue

<template >
    <div class="center">
        <ul>
            <li v-for="todo in items" :key="todo.id">
                <input type="checkbox" v-model="todo.isDone">
                <span v-bind:class="{done : todo.isDone}">{{todo.title}}</span>
                <button class="delbtn" @click="$emit('deleteitem', todo.id)">[削除]</button>
            </li>
            <li v-show="!items.length " style="text-align: center;">Todo項目はありません</li>
        </ul>
    </div>
</template>

<script>
export default {
    name: 'Todos',
    props: {
        items:{
            type: Array,
            required: true
        }
    }
}
</script>

以上になります。😀

Follow me!