computedは、ほかのデータから計算できる値を、キャッシュ付きで扱う仕組みです。テンプレートに複雑な式を書かずに済み、元のデータが変わったときだけ再計算されるため、無駄がありません。この記事では、Vue 3(<script setup>)を前提に、基本から、使い分け、つまずきやすい点までを動くコードで整理します。
基本の使い方
computed()に関数を渡すと、その関数が返す値が、算出プロパティになります。関数の中で使ったリアクティブな値(firstNameとlastName)が変わったときだけ、再計算されます。それ以外のときは、前回の結果をそのまま返します。
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
// firstName か lastName が変わったときだけ再計算される
const fullName = computed(() => `${firstName.value} ${lastName.value}`)
</script>
<template>
<p>{{ fullName }}</p>
</template>
Options APIの場合
Options APIでは、computedオプションに書きます。既存のコードを読むときのために、書き方を押さえておくと安心です。
export default {
data() {
return {
firstName: 'John',
lastName: 'Doe'
}
},
computed: {
fullName() {
return `${this.firstName} ${this.lastName}`
}
}
}
methodsとの違いと使い分け
同じ計算は、メソッドでも書けます。違いは、キャッシュの有無です。computedは、依存する値が変わらない限り、結果を使い回します。メソッドは、テンプレートに書いた回数だけ、再描画のたびに実行されます。
<script setup>
import { ref, computed } from 'vue'
const number1 = ref(10)
const number2 = ref(20)
const sum = computed(() => number1.value + number2.value)
function calculateSum() {
return number1.value + number2.value
}
</script>
<template>
<!-- computed:依存する値が変わらない限り、キャッシュされた値を返す -->
<p>{{ sum }}</p>
<p>{{ sum }}</p>
<!-- methods:書いた回数だけ、再描画のたびに実行される -->
<p>{{ calculateSum() }}</p>
<p>{{ calculateSum() }}</p>
</template>
重い計算や、テンプレートの複数の場所で使う値はcomputedが向いています。クリックなどの操作に応じて処理を実行したいときは、メソッドを使います。
実用例:配列のフィルタリングとソート
computedの代表的な使い道は、検索や並べ替えのような、データから表示用の配列を作る処理です。入力欄の値が変わると、絞り込み結果が自動で更新されます。
<script setup>
import { ref, computed } from 'vue'
const keyword = ref('')
const sortDesc = ref(false)
const items = ref([
{ id: 1, name: 'Apple', price: 120 },
{ id: 2, name: 'Banana', price: 80 },
{ id: 3, name: 'Cherry', price: 300 }
])
const visibleItems = computed(() => {
// 絞り込み(filter は新しい配列を返すので、元の配列は変わらない)
const filtered = items.value.filter(item =>
item.name.toLowerCase().includes(keyword.value.toLowerCase())
)
// sort は元の配列を書き換えるので、先にコピーしてから並べ替える
return [...filtered].sort((a, b) =>
sortDesc.value ? b.price - a.price : a.price - b.price
)
})
</script>
<template>
<input v-model="keyword" placeholder="名前で絞り込み">
<label><input type="checkbox" v-model="sortDesc"> 価格の高い順</label>
<ul>
<li v-for="item in visibleItems" :key="item.id">
{{ item.name }}:{{ item.price }}円
</li>
</ul>
</template>
ここでsort()の前に[...filtered]とコピーしているのが重要です。sort()やreverse()は、元の配列を書き換えてしまいます。computedの中で元のデータを書き換えると、思わぬ副作用が出るため、必ずコピーしてから使います。検索付きのリストの作り方は検索フィルター付きリストの作成、v-forとの組み合わせはv-forで配列やオブジェクトをループして表示する方法をご覧ください。
computedで守るルール
公式のベストプラクティスでは、getterは「計算して値を返すだけ」にします。
- getterの中で、ほかの状態を変更しない
- getterの中で、非同期のリクエストを送らない、DOMを変更しない
- 返り値は読み取り専用として扱い、変更しない。変えたいなら、元のデータを更新する
- リアクティブでない値(
Date.now()など)だけに依存すると、更新されない
<script setup>
import { ref, computed } from 'vue'
const count = ref(0)
const logs = ref([])
const numbers = ref([3, 1, 2])
// NG:getter の中で、ほかの状態を変更している
const doubled = computed(() => {
logs.value.push('計算した') // 副作用
return count.value * 2
})
// NG:元の配列を破壊的に並べ替えている
const sorted = computed(() => numbers.value.sort())
// NG:Date.now() はリアクティブではないので、更新されない
const now = computed(() => Date.now())
// OK:コピーしてから並べ替える
const sortedOk = computed(() => [...numbers.value].sort())
</script>
データの変化をきっかけに、保存やAPI呼び出しなどの処理を実行したいときは、computedではなくwatchを使います。使い分けはwatchを活用してデータの変化を監視する方法で解説しています。
setterを持つcomputed
computedは、通常は読み取り専用ですが、getとsetを持つオブジェクトを渡すと、書き込みもできます。fullNameに値を代入すると、setが呼ばれ、元のfirstNameとlastNameが更新されます。
<script setup>
import { ref, computed } from 'vue'
const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
// 半角・全角のスペースで区切る。1語目を名、残りを姓にする
const [first = '', ...rest] = newValue.trim().split(/\s+/)
firstName.value = first
lastName.value = rest.join(' ')
}
})
</script>
<template>
<input v-model="fullName">
<p>名:{{ firstName }}/姓:{{ lastName }}</p>
</template>
この例では、スペースで区切って、1語目を名、残りを姓にしています。v-modelに結びつけると、入力のたびにsetterが呼ばれます。ミドルネームのある名前や、区切りのない名前は、きれいに分けられないので、実用では入力欄を分けるほうが確実です。v-modelの使い方は双方向データバインディングでフォームの入力値を反映させる方法をご覧ください。
export default {
data() {
return { firstName: 'John', lastName: 'Doe' }
},
computed: {
fullName: {
get() {
return `${this.firstName} ${this.lastName}`
},
set(newValue) {
const [first = '', ...rest] = newValue.trim().split(/\s+/)
this.firstName = first
this.lastName = rest.join(' ')
}
}
}
}
関連記事
watchとwatchEffectの違いはwatch・watchEffectの違いと使い分け- 条件による表示切り替えはv-ifで条件分岐させる方法
よくある質問(FAQ)
computedです。結果がキャッシュされ、依存する値が変わったときだけ再計算されます。クリックなどの操作に応じて実行する処理は、メソッドにします。watchなどで行います。Date.now()を返すcomputedは、最初の1回しか計算されません。リアクティブなデータに依存させてください。sort()やreverse()は、元の配列を書き換えるためです。[...array].sort()のように、先にコピーしてから並べ替えてください。まとめ
computedは、データから計算できる値を、キャッシュ付きで扱います。getterには副作用を書かず、並べ替えは先にコピーし、保存やAPI呼び出しはwatchに任せる、という使い分けを押さえておけば、読みやすく、壊れにくいコードになります。
