Vueのcomputedをゆるく解説してみる
🌱 はじめに
Vue.jsを使いたてでよく「結局computedってなんなん...??」「これはcomputedにした方がいい...??」と悩んでいたのでその考え方として。
公式ドキュメントを元にゆるい日本語で説明していきます。
🤔 computedって??
あるデータをウニャウニャして新しいデータを算出する。
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
// authorの中のbooks の配列の長さを測ってpublishedBooksMessageという新たなデータを作る
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
👍 いいこと
再計算が自動でできる
さっきのコードをちょっと増やしてHTML部分も追加して、本を追加・削除するものを作ってみる。
<script setup>
import { reactive, computed, ref } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const newBook = ref('');
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
function addNewBook() {
author.books.push(newBook.value);
newBook.value = '';
}
function removeBook(index) {
author.books.splice(index, 1);
}
</script>
<template>
<div>
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
<ul>
<li v-for="(book, index) in author.books" :key="index">
{{ book }}
<button @click="removeBook(index)">Remove</button>
</li>
</ul>
<input type="text" v-model="newBook" />
<button @click="addNewBook">Add Book</button>
</div>
</template>
こんな感じのものだとデータが変わりやすいとか画面更新せずに変更したい、みたいな時にとても使える。
computed
を使う何よりの理由。
コードが見やすい
先ほどのコードを基に
<p>Has published books:</p>
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
みたいな対応をしたいとなったら
<p>Has published books:</p>
<span>{{ publishedBooksMessage }}</span>
にした方がスッキリする。
責務分けできる
さっきと似ているが、HTMLタグ書くところは構成を見せるところ、ロジックは <script></script>
内に書くという役割のすみ分け(= 責務分け)を明確にしてあげる。
<p>Has published books:</p>
<!-- 構成とロジックが一緒になっている -->
<span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
<script setup>
import { reactive, computed, ref } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const newBook = ref('');
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
function addNewBook() {
author.books.push(newBook.value);
newBook.value = '';
}
function removeBook(index) {
author.books.splice(index, 1);
}
</script>
<template>
<p>Has published books:</p>
<!-- ロジックをcomputed()内にしてここでは構成だけ -->
<span>{{ publishedBooksMessage }}</span>
</template>
可読性の向上に加えて、コンポーネントを再利用しやすくなる。
また、条件が変わったときにHTML構成内から探すのではなくて computed
から探して書き換える方がやりやすさが上がる。
❓ 通常の変数扱いじゃダメなん??
こんな感じ。
<script setup>
import { reactive, ref } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const publishedBooksMessage = ref(author.books.length > 0 ? 'Yes' : 'No')
</script>
これ自体は問題なく動くが、あくまでも publishedBooksMessage
は初期値のauthor.bookの要素数に依存するので、要素が追加・削除されてもリアクティブには変更できないことに注意。
🚀 関数扱いじゃダメなん??
処理っぽい感じで関数として扱ってみたいと思う。
<script setup>
import { reactive, ref } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const newBook = ref('')
function publishedBooksMessage() {
return author.books.length > 0 ? 'Yes' : 'No';
}
function addNewBook() {
if (newBook.value.trim() !== '') {
author.books.push(newBook.value.trim())
newBook.value = ''
}
}
function removeBook(index) {
author.books.splice(index, 1)
}
</script>
<template>
<div>
<p>Has published books:</p>
<span>{{ publishedBooksMessage() }}</span>
<ul>
<li v-for="(book, index) in author.books" :key="index">
{{ book }}
<button @click="removeBook(index)">Remove</button>
</li>
</ul>
<input type="text" v-model="newBook"/>
<button @click="addNewBook">Add Book</button>
</div>
</template>
https://ja.vuejs.org/guide/essentials/computed.html#computed-caching-vs-methods にも書いてあるように実はこれ、 computed
とした時と同じように動かせる。
その上で公式ドキュメントは↓を computed
のセールスポイントとしている。
算出プロパティはリアクティブな依存関係にもとづきキャッシュされる
???
ということで別のコードで考えてみる。
<script setup>
import { computed, ref } from "vue";
const count = ref(0)
const computedResult = computed(() => {
console.log('Computed calculated');
return count.value * 2;
})
function calculateComputed() {
// あえて同じ値を入れることでcomputedを発動しようとしてみる
count.value = count.value;
}
function methodResult() {
console.log('Method calculated');
return count.value * 2;
}
</script>
<template>
<div>
<input type="number" v-model="count" />
<button @click="calculateComputed">Run Computed</button>
<button @click="methodResult">Run Method</button>
<p>{{ computedResult }}</p>
<p>{{ methodResult() }}</p>
</div>
</template>
みたいなコードがあった時に
inputタグに値を入れた状態で
Run Computed
ボタンを連打 → console.log()
が動かない
Run Method
ボタンを連打 → console.log()
が動く
ということっぽい。
関数にすると値が変わっていなくても都度処理が走るので
重くなってしまう部分をcomputedにするということも利点の1つとのこと。
🔧 getter関数とsetter関数
computed
にはgetter関数とsetter関数という概念が存在する。
getter → getなので値を取得する
setter → setなので値を設定する
という役割がある。
もっと砕いていうと
getter → そのcomputedの値を算出する
setter → computedの値から他の変数などを代入したり再計算したりする
って感じみたい。
今までのように特に明示しない書き方はgetter関数とみなされている。
<script setup>
import { computed, ref } from "vue";
const firstName = ref('');
const lastName = ref('');
const fullName = computed({
// getter 関数
get() {
console.log('Getter called');
return firstName.value + ' ' + lastName.value;
},
// setter 関数
set(value) {
console.log('Setter called');
const names = value.split(' ');
firstName.value = names[0];
lastName.value = names[1];
}
})
</script>
<template>
<div>
<p>名字: {{ lastName }}</p>
<p>フルネーム: {{ fullName }}</p>
<input v-model="lastName" placeholder="Enter your last name" />
<input v-model="fullName" placeholder="Enter your full name" />
</div>
</template>
みたいなコードがあった場合に、 firstName
や lastName
に変更が入ると今まで通り fullName
のgetter関数が動いて表示される。
加えて fullName
側にも変更が入ると firstName
や lastName
に変更が入る。
💡 computedの使い方
ここ にはベストプラクティスと書いていたけどあくまでも使い方として。
computed
は他の変数などの書き換えついでに変わってしまう値(公式では「派生した状態」「スナップショット」って言ってる)。
getter関数内では他の変数を書き換えたりDOM操作したりはしたらダメだよ〜ということ。
じゃあどうするの??というのはwatchを使ってねとのこと。
※ setter関数で書く場合は問題なさそう
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const publishedBooksMessage = computed(() => {
author.books.value.push('Vue 5 - Cartoon');
return author.books.length > 0 ? 'Yes' : 'No'
})
</script>
また、 computed
は他の要素の書き換えついでに変わってしまう値なので computed
を直に書き換えることもNG。
元の書き換え要素を書き換えてね。
<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
name: 'John Doe',
books: [
'Vue 2 - Advanced Guide',
'Vue 3 - Basic Guide',
'Vue 4 - The Mystery'
]
})
const publishedBooksMessage = computed(() => {
return author.books.length > 0 ? 'Yes' : 'No'
})
function addNewBook() {
publishedBooksMessage.value = 'Yes';
}
</script>
アルサーガパートナーズ株式会社のエンジニアによるテックブログです。11月14(木)20時〜 エンジニア向けセミナーを開催!詳細とご応募は👉️ arsaga.jp/news/pressrelease-cheer-up-project-november-20241114/
Discussion