LoginSignup
135
66
お題は不問!Qiita Engineer Festa 2023で記事投稿!

【Vue】reactive()って要らなくね?ref()だけでよくね?

Last updated at Posted at 2023-06-26

使用技術

  • Vue3 + Nuxt3
    • Composition API
  • TypeScript

ワイ、お問い合わせフォームを作りたい

ワイ「Vue の Composition API を使って、お問い合わせフォームを作るで!」
ワイ「フォームのデザインはこんな感じや!」

スクリーンショット 2023-06-25 15.07.23.png

ワイ「いや〜、我ながらイケてるデザインやな!」
ワイ「ほな、Composition APIを使ってフォームの状態を管理していくで〜」

フォームの状態を管理するには?

ref()またはreactive()

ワイ「Composition APIの場合、状態を管理するには」
ワイ「ref()またはreactive()を使うんやったな」
ワイ「この2つは何が違うんやろ?」
ワイ「確か───」

  • ref()
    • 文字列・数値・真偽値などの、プリミティブな値を保持できる
  • reactive()
    • オブジェクト・配列・Map・Setなどの状態を保持できる

ワイ「↑こんな感じで使い分けるんやっけ・・・?」
ワイ「しばらく前に勉強したけど、もう忘れてもうたわ!」
ワイ「とりあえず、両方とも触ってみよか!」

ref()を使ってフォームの状態を管理してみる

ワイ「さあ、コードを書いていくで!」

script部分

<script setup lang="ts">
// お問い合わせ内容の状態を管理
const contentRef = ref("");
</script>

ワイ「おお、TypeScript部分はたったこれだけでええんか1

template部分

<template>
  <form>
    <h1>お問い合わせフォーム</h1>
    <dl>
      <dt>お問い合わせ内容</dt>
      <dd>
        <textarea type="text" name="content" v-model="contentRef" />
      </dd>
    </dl>
    <button>送信</button>
  </form>
</template>

ワイ「テンプレート部分は↑こんな感じやな」
ワイ「おなじみv-modelを使って双方向バインディングしてやる感じやな」

結果

スクリーンショット 2023-06-25 15.10.36.png

できた

ワイ「おお、できたで!」
ワイ「入力した内容が、ちゃんと下側の部分に反映されとる」
ワイ「ref()を使って、文字列の状態を管理することができたで!」

リセット機能もつけたい

ワイ「入力内容をリセットするボタンもつけたいな」
ワイ「追加してみよか」

    // お問い合わせ内容の状態を管理
    const contentRef = ref("");

+   // フォームの状態をリセットする関数
+   const resetContentRef = () => {
+     contentRef.value = ""
+   };

ワイ「↑こうやな」
ワイ「ref()で管理してる値にアクセスするには」
ワイ「.valueって書いてやらなアカンのやな」

<template>内では.valueを書く必要はありません

ワイ「ほんで、html も追加してやらんとな」

<template>
  <form>
    <h1>お問い合わせフォーム</h1>
    <dl>
      <dt>お問い合わせ内容</dt>
      <dd>
        <textarea type="text" name="content" v-model="contentRef" />
      </dd>
    </dl>
    <button>送信</button>
+   <button type="button" @click="resetContentRef">リセット</button>
  </form>
</template>

ワイ「↑こうやな」

ワイ「ほな、リセットボタンをポチ!」

結果

スクリーンショット 2023-06-25 15.25.05.png

ワイ「お、ちゃんと空っぽになったで」
ワイ「むっちゃ簡単やな」

ワイ「ほな、次はreactive()を使って同じことをしてみよか」

reactive()を使ってフォームの状態を管理してみる

ワイ「ほなreactive()を使って書いてみるで」

<script setup lang="ts">
// お問い合わせ内容の状態を管理
const contentReactive = reactive("");
</script>

ワイ「あれ?」

スクリーンショット 2023-06-25 15.17.03.png

ワイ「VSCodeに、赤い波線が表示されとる・・・」
ワイ「TypeScriptのエラーが出てもうたみたいや」

エラー内容
型 'number' の引数を 型 'object' のパラメーターに割り当てることはできません。

ワイ「???」
ワイ「つまり・・・?」

reactive()では、プリミティブ型の値を保持できない

ワイ「なるほどな」
ワイ「reactive()の場合は、文字列・数値・真偽値などの、プリミティブな値を保持できひんのやな」
ワイ「reactive()は、オブジェクト・配列・Map・Setなどの状態を管理するためのAPIなんやな」

なので、オブジェクトにする

ワイ「プリミティブ値がダメなら」
ワイ「こう、例えばvalueというプロパティを持ったオブジェクトにしてやれば」
ワイ「reactive()で状態を管理できるな」

    // お問い合わせ内容の状態を管理
    const contentReactive = reactive({ value: "" });

ワイ「ほんで、リセットボタンは───」

    // フォームの状態をリセットする関数
    const resetContentReactive = () => {
      contentReactive.value = "";
    };

ワイ「↑こうやな」

なんか、ref()と同じ感じになった

ワイ「結局、ref()を使った場合とほとんど同じ感じになったな」
ワイ「reactive()ではプリミティブ値を保持できないから」
ワイ「わざわざvalueというプロパティを持ったオブジェクトを定義したけど」
ワイ「それなら普通にref()を使えばいいかもな」
ワイ「今のところ、ref()reactive()の違いがあんまり分からへんな・・・」

ワイ「とりあえず続きを実装していこか」

Next: メールアドレス入力欄が必要

ワイ「そういえば、メールアドレスの入力欄が必要や」
ワイ「そうやないと、お問い合わせに返信できひんわ」
ワイ「ほな、コードを修正していくで」

まずはref()を使ってやってみる

ワイ「さっきは、お問い合わせ内容の本文だけだったからプリミティブ値だったけど」
ワイ「今度はメールアドレスも必要やから───」

-   const contentRef = ref("");
+   const formRef = ref({
+     email: "",
+     content: "",
+   });

ワイ「↑こうやな」
ワイ「emailcontentというプロパティを持ったオブジェクトに変えてみたで」

ワイ「ref()はプリミティブ値専用というわけじゃなくて」
ワイ「オブジェクトの状態も管理できるんやな」
ワイ「知らんかったわ」

リセット用の関数も修正

ワイ「フォームを空っぽにするわけやから───」

    // フォームの状態をリセットする関数
    const resetFormRef = () => {
      // フォームの状態を上書き
      formRef.value = {
        email: "",
        content: "",
      };
    };

ワイ「↑こうやな!」
ワイ「簡単や!」

今度はreactive()でやってみる

ワイ「お問い合わせ内容とメールアドレスの状態を管理したいから───」

-   const contentReactive = reactive({ value: "" });
+   let formReactive = reactive({
+     email: "",
+     content: "",
+   });

ワイ「↑こうやな!」

ワイ「ほんで、リセットボタン用の関数も書き直さんとな」

    // フォームの状態をリセットする関数
    const resetContentReactive = () => {
      // フォームの状態を上書き
      formReactive = {
        email: "",
        content: "",
      };
    };

ワイ「↑こうやな」

ワイ「ほな、リセットボタンをポチ!」

結果

スクリーンショット 2023-06-25 16.26.30.png

ワイ「あれ、消えてへん・・・・」

reactive()の場合、オブジェクト全体を上書きできない

ワイ「なるほど、reactive()の場合は丸ごと置き換えはできひんのか」
ワイ「ほな───」

    // フォームの状態をリセットする関数
    const resetFormReactive = () => {
      // 1プロパティずつ上書き
      formReactive.email = "";
      formReactive.content = "";
    };

ワイ「↑こうせなアカンのやな」
ワイ「プロパティ1つ1つに対して空の値を入れてやらなアカンわけやな」
ワイ「プロパティが沢山ある場合には面倒そうやな」
ワイ「ref()と違って.valueを書かなくていいのは楽やけどな」

もしくは、オブジェクト全体をvalueプロパティに入れる2

ワイ「もしくは、管理したい全ての状態を───」

    const formReactive = reactive({
      value: {
        email: "",
        content: "",
      }
    });

ワイ「↑こう、valueプロパティとかで包んでやれば」

    // フォームの状態をリセットする関数
    const resetFormReactive = () => {
      // フォームの状態を上書き
      formReactive.value = {
        email: "",
        content: "",
      };
    };

ワイ「↑こう、丸ごと上書きできるわけやな」

でも、その場合<template>内で.valueを省略できない

ワイ「ref()を使った場合、<template>内では.valueを書かんくてよかったけど」
ワイ「reactive()の中に自分でvalueプロパティを定義した場合は───」

    <dl>
      <dt>メールアドレス</dt>
      <dd>
        <input type="text" name="email" v-model="formReactive.value.email">
      </dd>
      <dt>お問い合わせ内容</dt>
      <dd>
        <textarea type="text" name="content" v-model="formReactive.value.content" />
      </dd>
    </dl>

ワイ「↑こう、<template>の中でも.valueって書かないといけないんやな」

ワイ「これならref()を使ったほうがいい気がするな」

他にも、reactive()には制限が

分割代入NG

const { email, content } = formReactive;

↑こう、分割代入してしまうとリアクティビティが失われます。
つまり、値を更新しても画面と連動しなくなります。

toRefs()を使って、全プロパティをRefに変換してやれば回避できます

でも、そこはref()も同じようなもの

const { email, content } = formRef.value;

↑このようにref.valueを分割代入した場合も、リアクティビティが失われます。

※この場合もtoRefs()を使えばリアクティビティを保持できます

ref()reactive()の違いまとめ

ref()

  • メリット
    • どんな型の値でも保持できる
      • プリミティブ値もOK
    • 状態を丸ごと上書きできる
  • デメリット
    • .valueって書かないといけない
      • でも<template>の中では書かなくてOK

reactive()

  • メリット
    • <script>内で何度も.valueを書かなくていい
  • デメリット
    • プリミティブ値を管理できない
      • オブジェクトのプロパティとしてなら保持できる
    • 状態を丸ごと上書きできない
      • プロパティ1つずつ上書きする必要がある
      • オブジェクトのプロパティなら丸ごと上書き可能

共通

  • 分割代入するときは注意が必要
    • リアクティビティを保持したい場合、toRefs()を使うべし

ref()だけでよくね?reactive()要らなくね?

reactive()には「プリミティブ値を管理できない」「状態を丸ごと上書きできない」といった制限があるため、割と使いづらく感じました。

nullも管理できないため、object | null的な型も使用できません。

正直「ref()だけでよくね?reactive()要らなくね?」と思いました。
「似たような機能が2つあるより、1つしかない方がいいんじゃ・・・?」と。

「使い分けできる」というメリットよりも
「両方キャッチアップして吟味しなければならない」というデメリットの方が大きく感じました。
人によって、どちらを使うか「揺れ」も生じますし。

でも、ref()だと何度も.valueを書かなくちゃいけないじゃん?

確かにそこは手間ではありますが、
逆にコードは読みやすくなりそうだと感じました。

ref()の場合

obj.value.prop1 = "";

↑「.value経由で値を再代入しているから、これはref()で管理している状態だな!」
と推測できます。

さらに、TypeScriptを使用していれば、ref()で作った値だということが型情報としても可視化されます。

スクリーンショット 2023-06-25 17.29.01.png

reactive()の場合

obj.prop1 = "";

reactive()で状態管理しているオブジェクトなのか、それとも「ただのオブジェクト」なのか見分けがつきません。

型情報を見てみても・・・

VSCodeで型情報を見てみても、reactive()で作ったオブジェクトであることは分かりませんでした。

スクリーンショット 2023-06-25 17.24.55.png

公式にもref()推奨らしい

公式ドキュメントにも「主にref()を使うように」という旨が記載されていました。

reactive()APIにはいくつかの制限があります

(中略)

このような制約があるため、リアクティブな状態を宣言するための主要な API として
ref()を使用することを推奨します。

みなさん、基本的にref()を使っていきましょう。

【おまけ】 実はref()の中で、内部的にreactive()が使われている

非プリミティブ値(オブジェクトなど)をref()に渡した場合、内部的にreactive()が使われます。
なので「reactive()は完全に不要」ということにはなりません。
ただし、ユーザから使えるAPIとしては要らないかなって思いました。

参考文献

新しい記事もよろしくやで!

  1. Nuxt3を使用しているため、refのimportは不要になっています。

  2. プロパティ名は別にvalueじゃなくてもいいです

135
66
5

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
135
66