1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Vue3の`computed`を使って、評価表示をリアクティブにする

1
Posted at

今日はVue3のcomputedを触ったので、そのメモです。

画像解析結果の件数や判定を画面へ出すとき、元データから表示用の値を作る場面で使えそうだと思い、refだけで書いた場合と比べてみました。

まだ途中段階なので、勘違いなどあれば教えていただけるとうれしいです。

今日やったこと

scoreからGoodまたはBadという評価を作り、画面への表示がどう変わるかを確認しました。

最初は、refの値を使って通常の変数へ評価結果を代入してみました。

<script setup>
import { ref } from 'vue'

// ボタンを押すたびに増える、リアクティブなスコア
const score = ref(0)

// この行はセットアップ時に一度だけ実行される。
// score.value が後から変わっても、evaluation は自動では更新されない。
const evaluation = score.value > 3 ? 'Good' : 'Bad'
</script>

<template>
  <!-- テンプレート内では ref が自動で展開されるため、score.value は不要 -->
  <p>{{ score > 3 ? 'Good' : 'Bad' }}</p>

  <!-- evaluation は通常の文字列なので、最初に作られた結果のままになる -->
  <p>{{ evaluation }}</p>

  <p>Score: {{ score }}</p>

  <!-- ボタンを押すと score が増え、テンプレートは再描画される -->
  <button @click="score++">score + 1</button>
</template>

このコードでは、1つ目の<p>はボタンを押してscoreが変わるたびに、テンプレートの条件式が評価されます。一方で、evaluationは最初にBadとして作られると、その後にscoreが増えても変化しませんでした。

条件式を変数にまとめれば同じように表示が切り替わると思っていたので、ここで少し止まりました。

image.avif > 「見た目が似た条件式でも、計算されるタイミングが違うんだね。」

通常の変数への代入は、その時点の結果を入れる処理です。refを使っていても、.valueを読んで作った通常の文字列まで自動でリアクティブになるわけではない、と今は理解しています。

次に、computedで評価結果を作ってみました。

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

// 画面上のボタン操作で変化する元データ
const score = ref(0)

// score をもとにした「派生した値」を作る。
// score が変わると、この計算結果は古い状態として扱われ、
// 次に evaluation が必要になったタイミングで最新の内容が計算される。
const evaluation = computed(() => {
  console.log('computed が評価されました')

  // computed の getter は、画面に出したい計算結果を return する
  return score.value > 3 ? 'Good' : 'Bad'
})

console.log(evaluation) // computed が返す Ref オブジェクト自体
console.log(evaluation.value) // 現在の計算結果。「Bad」または「Good」
</script>

<template>
  <!-- 条件式をテンプレートへ直接書いた場合 -->
  <p>{{ score > 3 ? 'Good' : 'Bad' }}</p>

  <!-- computed もテンプレート内では自動展開される -->
  <p>{{ evaluation }}</p>

  <p>Score: {{ score }}</p>
  <button @click="score++">score + 1</button>
</template>

computedevaluationは、スクリプト内ではevaluation.valueとして読めます。テンプレート内ではrefと同じように中身が自動展開されるため、{{ evaluation }}と書けます。

画面としては、スコアが(3)より大きくなったタイミングで、評価表示がBadからGoodへ切り替わります。画像解析結果の信頼度や検出件数をもとに、一覧カードへ状態ラベルを出すような画面でも使えそうです。

Vue公式のcomputedプロパティの説明も見ながら、computedは「リアクティブな値から、別のリアクティブな表示用の値を作るもの」として読んでいます。

つまずいたところ

computedがいつ再評価されるのか、まだ言葉で説明しようとすると少し曖昧です。

最初は、scoreが更新された瞬間にcomputedの関数が必ず実行され続けるものだと思っていました。

image.avif > 「“監視している”と聞くと、ずっと働き続けている感じがしてしまう。」

今の理解では、computedは中で読んだリアクティブな値、今回ならscore.valueを依存関係として追跡します。scoreが変わると計算結果は古い状態になりますが、すぐに必ず計算し直すというより、evaluation.valueやテンプレートから次に必要とされたときに再評価されるようです。

また、依存する値が変わらないまま何度も参照された場合、前回の計算結果を使えるキャッシュもcomputedの特徴だと理解しています。条件式をテンプレートへ何度も書くより、評価に名前を付けてまとめられる点も読みやすさにつながりそうです。

もう1つ迷ったのは、computedの関数の引数です。

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

const score = ref(0)

// Vue 3.4 以降では、getter の引数で前回の計算結果を受け取れる。
// 初回は前回の値がないため、undefined になる可能性がある。
const evaluation = computed((previous) => {
  console.log('前回の評価:', previous)

  // 今回は score の現在値を使って、新しい評価を返す
  return score.value > 3 ? 'Good' : 'Bad'
})
</script>

入力時は引数をvalueという名前にしていましたが、これは現在のscoreの値ではなく、今の理解では前回の計算結果を受け取る引数です。今回のような単純な判定では使わないため、引数なしのcomputed(() => ...)のほうが混乱しにくそうです。

image.avif > 「引数があるなら現在のスコアだと思ってしまう。名前を付けるときも気を付けたい。」

さらに、computedの中へsetTimeoutを置いて、値が変わったときの動きを見ようとしました。ただ、computedは値を返すための計算に寄せて、副作用や非同期処理は入れないほうがよさそうです。

  • 副作用とは、外部の値を書き換える、通信を始める、タイマーを動かすなど、計算結果を返すこと以外の処理だと今は捉えています
  • setTimeoutやAPI通信をcomputedへ入れると、いつ実行されるかを追いにくくなりそうです
  • 値の変化に合わせて別の処理をしたい場合は、watchを使う場面かもしれないので、次に比べてみたいです

今の理解(メモ)

まだふわっとしていますが、今の自分の頭の中ではcomputedは「元データから画面用の値を作る場所」です。

  • refは、後から変化する値そのものを持つために使う。今回ならボタン操作で増えるscore
  • 通常のconst evaluation = ...は、その行を実行した時点の結果を持つ。scoreが変わっても、文字列のevaluationは変わらない
  • computedは、scoreのようなリアクティブな値をもとに、別のリアクティブな値を作る
  • computedの戻り値は読み取り専用のRefのように扱う。evaluation.value = 'Good'のように直接セットする用途ではない
  • テンプレートではrefcomputed.valueを省略できるため、画面側は{{ evaluation }}と比較的すっきり書ける
  • computedの中に別のcomputedを使うこともできそうだが、依存関係が複雑になりそうなので、まずは小さく分けるところから試したい

たとえば画像解析の一覧画面なら、元の解析データから「信頼度が基準以上か」「検出件数をどう表示するか」といった表示用の値を作る用途につながるかもしれません。

image.avif > 「元データを直接いじるより、“表示用に変換する場所”として考えると少し見えやすいかも。」

ただし、実際のデータ構造でどこまでcomputedへまとめると読みやすいかは、まだこれから確認したいです。

次にやりたいこと

小さな画面を作りながら、computedを使う場面と使わない場面を比べたいです。

  • 検索語をrefで持ち、computedで一覧を絞り込む。入力した文字に応じて画像や作品カードが減る画面を試したい
  • 解析結果の数値から表示ラベルを作る。GoodBadだけでなく、信頼度に応じた表示をどう分けると分かりやすいか確認したい
  • watchscoreの変化を受け取る例を作る。computedとの役割の違いを、同じデータで比べたい
  • 非同期処理はcomputedの外で扱う。検索APIやデータ読み込みを想定して、どこへ書くと整理しやすいか試したい

今日の整理

今の理解を、後で見返せるように短く表にしました。

用語・書き方 今の役割 画面での利用イメージ 次に確認したいこと
ref(0) 変化する元データを持つ ボタンで増えるスコア、検索フォームの入力値 オブジェクトや配列を持たせる場合
通常の変数への代入 その時点の計算結果を持つ 初期表示だけでよい固定文言 どこまでが再実行されないか
computed(() => ...) 元データから派生した値を作る スコアから評価表示、一覧の絞り込み結果 複数の条件を組み合わせる場合
evaluation.value スクリプト内でcomputedの値を読む 条件分岐や別の計算に使う ネストしたcomputedの依存関係
{{ evaluation }} テンプレートでcomputedの値を表示する GoodBadの表示切り替え フォームやカード表示との組み合わせ
watch 値の変化をきっかけに別処理を行う用途になりそう 検索条件変更後の処理など 非同期処理との使い分け

おわりに

今回はcomputedを使って、同じ条件式でも通常の変数へ入れる場合と、リアクティブな派生値として扱う場合では動きが異なると分かってきました。まだなんとなく動く段階ですが、画面で必要な表示を元データから組み立てる流れが少し見えてきた気がします。

今後は、画像解析結果を見やすく一覧表示したり、検索や条件によって表示を切り替えたりする画面につなげたいです。まずは小さな検索フォームで、computedの便利さをもう少し手で確認してみます。

学習中の内容なので、勘違いや理解不足もあると思います。もし気になる点があれば教えてもらえるとうれしいです。

 注意事項
 本ブログに掲載している内容は、私個人の見解であり、
 所属する組織の立場や戦略、意見を代表するものではありません。
 あくまでエンジニアとしての経験や考えを発信していますのでご了承ください。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?