[Vue3]コンポーザブル関数使ってv-forの中でcomputedを利用する

最近、Vue.jsはComposition API(<script setup>)かつTypeScriptで書くようにしています。

ところで昔から、Vue.jsを使っていると、v-forで利用している要素をメソッドではなくcomputedで計算したいなんて思う場面がたまにあります。そういう場合、v-forを使ってるタグを子コンポーネントにして、配列要素を属性で渡すようにして子コンポーネント内でcomputedを使うなんてことをやっていることもあったのですが、VuetifyなどUIフレームワークを使っているとそれが難しいこともあります(テーブル内のtrをv-forでループしたい場合、trタグだけで子コンポーネントにするのもなんだか微妙な気もしますし)。

ただ、Vue3のComposition APIの場合、コンポーザブル関数でcomputed変数を返すようにし、それを配列に含めるようにしたらいいのではないかと思い、やってみました。

<script lang="ts">
import { ref, reactive, computed, toRefs } from "vue";

export interface TypeScore {
  name: string;
  japanese: number;
  english: number;
  mathmatic: number;
}

// スコア用のコンポーザブル関数
export const useScore = (score: Partial<TypeScore> = {}) => {
  const scoreObj = reactive<TypeScore>({
    name: "",
    japanese: 0,
    english: 0,
    mathmatic: 0,
  });
  Object.assign(scoreObj, score);

  const totalScore = computed(
    () => scoreObj.japanese + scoreObj.english + scoreObj.mathmatic
  );

  return {
    ...toRefs(scoreObj),
    totalScore,
  };
};
</script>

<script setup lang="ts">
// ReturnTypeで関数の戻り値の型を取得する
const scores = ref<ReturnType<typeof useScore>[]>([]);

scores.value.push(
  // reaactive()を指定しないと型エラーになる(実際は、無くても動くよう)
  reactive(
    useScore({
      name: "田中",
      japanese: 60,
      english: 70,
      mathmatic: 40,
    })
  ),
  reactive(
    useScore({
      name: "佐藤",
      japanese: 50,
      english: 50,
      mathmatic: 80,
    })
  )
);

const name = ref("");

const addScore = () => {
  if (!name.value) return;

  scores.value.push(
    reactive(
      useScore({
        name: name.value,
      })
    )
  );
  name.value = "";
};
</script>
<template>
  <h1>v-forの中でcomputedを使う</h1>

  <div>
    名前:<input type="text" v-model="name" @keydown.enter="addScore" /><input
      type="button"
      value="追加"
      @click="addScore"
    />
  </div>
  <table class="score_table">
    <thead>
      <tr>
        <th>名前</th>
        <th>国語</th>
        <th>英語</th>
        <th>数学</th>
        <th>合計</th>
      </tr>
    </thead>
    <tbody>
      <tr v-for="score in scores" :key="score.name">
        <td>{{ score.name }}</td>
        <td>
          <input type="number" v-model="score.japanese" min="0" max="100" />
        </td>
        <td>
          <input type="number" v-model="score.english" min="0" max="100" />
        </td>
        <td>
          <input type="number" v-model="score.mathmatic" min="0" max="100" />
        </td>
        <td>{{ score.totalScore }}</td>
      </tr>
    </tbody>
  </table>
</template>

サンプル:v-forの中でcomputedを使う

国語、英語、数学の点数にたいして、computedでその合計点数を表示するようなサンプルです。
ようは、配列変数に、リアクティブ変数とcomputed変数をまとめたオブジェクトをいれているわけです。こうすることで、v-forで使えます。

ただ、よく分からないのが、TypeScriptを使っていると配列変数に入れる際に、「reactive(useScore({}))」というようにreactiveをつけないといけないということ。これを付けないと型エラーで怒られるようです。

でも、どうやら書かなくても動きはするようなんですよね…。わざわざ「reactive」とつけるのは無駄なような気もするので、誰かもっといい方法を知っていたら教えてください。

コメント

タイトルとURLをコピーしました