採点されません。匿名です。 間違えることは学習の一部です。読むだけでもかまいません。

Vue — computed

テクノロジ系 / L03 lifecycle — ライフサイクルと算出プロパティ

Vue — computed

動機 — テンプレ内で計算が複雑

<template>
  <p>{{ items.filter(i => i.active).map(i => i.name).join(", ") }}</p>
</template>

→ テンプレが読みづらい。

派生値は computed に切り出す

computed の基本

<script setup>
import { ref, computed } from "vue";

const items = ref([
  { name: "A", active: true },
  { name: "B", active: false },
  { name: "C", active: true },
]);

const activeNames = computed(() =>
  items.value.filter(i => i.active).map(i => i.name).join(", ")
);
</script>

<template>
  <p>{{ activeNames }}</p>
</template>

computed(() => ...) で派生値を定義。

自動キャッシュ

<template>
  <p>{{ activeNames }}</p>
  <p>{{ activeNames }}</p>
  <p>{{ activeNames }}</p>
</template>

3回参照しても 1回しか計算されない

依存 (items) が変わるまでキャッシュされる。

→ React の useMemo(fn, [deps]) と同じだが、依存配列を自分で書かなくていい

Vue が依存を自動追跡する。

関数との違い

<script setup>
function getActiveNames() {
  return items.value.filter(...).map(...).join(", ");
}

const activeNames = computed(() => items.value.filter(...).map(...).join(", "));
</script>

<template>
  <p>{{ getActiveNames() }}</p>   <!-- 毎回呼ばれる -->
  <p>{{ activeNames }}</p>         <!-- キャッシュ -->
</template>

重い計算は 必ず computed

computed setter (双方向)

<script setup>
const firstName = ref("Taro");
const lastName = ref("Yamada");

const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value: string) => {
    const [f, l] = value.split(" ");
    firstName.value = f;
    lastName.value = l;
  },
});
</script>

<template>
  <input v-model="fullName" />
</template>

computed({ get, set }) で書き込み可能に。

v-model に直接バインドできる。

TypeScript で型

<script setup lang="ts">
const count = ref(0);

// 推論
const doubled = computed(() => count.value * 2);   // ComputedRef<number>

// 明示
const tripled = computed<number>(() => count.value * 3);
</script>

まとめ

  • 派生値は computed に切り出す
  • 自動キャッシュ (依存が変わるまで再計算しない)
  • 依存は 自動追跡 (React の useMemo より楽)
  • setter で双方向にもできる
  • 重い計算は必ず computed (関数だと毎回実行)

次は watch / watchEffect

考えてみよう

既定では正解・不正解の判定はしません。自分のペースで「答えを見る」を。採点してほしい場合だけ「採点してほしい」を押してください。

Lv1: 基本 — 派生値

問題

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

const firstName = ref("Taro");
const lastName = ref("Yamada");

const fullName = computed(() => `${firstName.value} ${lastName.value}`);
const initials = computed(() => firstName.value[0] + lastName.value[0]);
</script>

<template>
  <input v-model="firstName" />
  <input v-model="lastName" />
  <p>Full: {{ fullName }}</p>
  <p>Initials: {{ initials }}</p>
</template>

合格基準

入力に応じて派生値が更新。

Lv2: 応用 — キャッシュを観察

問題

<script setup>
const count = ref(0);

function getDoubled() {
  console.log("function called");
  return count.value * 2;
}

const doubled = computed(() => {
  console.log("computed called");
  return count.value * 2;
});
</script>

<template>
  <p>{{ getDoubled() }}</p>     <!-- 毎レンダー console.log -->
  <p>{{ getDoubled() }}</p>     <!-- 同じレンダー内でも毎回呼ぶ -->
  <p>{{ doubled }}</p>           <!-- キャッシュ -->
  <p>{{ doubled }}</p>           <!-- 同じレンダー内では再計算なし -->
  <button @click="count++">+</button>
</template>

console を観察。

合格基準

  • 関数: 毎回呼ばれる
  • computed: 依存変化時のみ
  • 「重い計算は computed」を理解

Lv3: 発展 — computed setter

問題

<script setup>
const firstName = ref("Taro");
const lastName = ref("Yamada");

const fullName = computed({
  get: () => `${firstName.value} ${lastName.value}`,
  set: (value: string) => {
    const [f, l] = value.split(" ");
    firstName.value = f ?? "";
    lastName.value = l ?? "";
  },
});
</script>

<template>
  <input v-model="fullName" />     <!-- 双方向 -->
  <p>First: {{ firstName }}</p>
  <p>Last: {{ lastName }}</p>
</template>

合格基準

  • v-model に computed をバインド
  • 双方向動作

提出

submission.md に3問の回答を。