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問の回答を。