使用技術
- Vue3 + Nuxt3
- Composition API
- TypeScript
ワイ、お問い合わせフォームを作りたい
ワイ「Vue の Composition API を使って、お問い合わせフォームを作るで!」
ワイ「フォームのデザインはこんな感じや!」
ワイ「いや〜、我ながらイケてるデザインやな!」
ワイ「ほな、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
を使って双方向バインディングしてやる感じやな」
結果
できた
ワイ「おお、できたで!」
ワイ「入力した内容が、ちゃんと下側の部分に反映されとる」
ワイ「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>
ワイ「↑こうやな」
ワイ「ほな、リセットボタンをポチ!」
結果
ワイ「お、ちゃんと空っぽになったで」
ワイ「むっちゃ簡単やな」
ワイ「ほな、次はreactive()
を使って同じことをしてみよか」
reactive()
を使ってフォームの状態を管理してみる
ワイ「ほなreactive()
を使って書いてみるで」
<script setup lang="ts">
// お問い合わせ内容の状態を管理
const contentReactive = reactive("");
</script>
ワイ「あれ?」
ワイ「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: "",
+ });
ワイ「↑こうやな」
ワイ「email
とcontent
というプロパティを持ったオブジェクトに変えてみたで」
ワイ「ref()
はプリミティブ値専用というわけじゃなくて」
ワイ「オブジェクトの状態も管理できるんやな」
ワイ「知らんかったわ」
リセット用の関数も修正
ワイ「フォームを空っぽにするわけやから───」
// フォームの状態をリセットする関数
const resetFormRef = () => {
// フォームの状態を上書き
formRef.value = {
email: "",
content: "",
};
};
ワイ「↑こうやな!」
ワイ「簡単や!」
今度はreactive()
でやってみる
ワイ「お問い合わせ内容とメールアドレスの状態を管理したいから───」
- const contentReactive = reactive({ value: "" });
+ let formReactive = reactive({
+ email: "",
+ content: "",
+ });
ワイ「↑こうやな!」
ワイ「ほんで、リセットボタン用の関数も書き直さんとな」
// フォームの状態をリセットする関数
const resetContentReactive = () => {
// フォームの状態を上書き
formReactive = {
email: "",
content: "",
};
};
ワイ「↑こうやな」
ワイ「ほな、リセットボタンをポチ!」
結果
ワイ「あれ、消えてへん・・・・」
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()
で作った値だということが型情報としても可視化されます。
reactive()
の場合
obj.prop1 = "";
↑reactive()
で状態管理しているオブジェクトなのか、それとも「ただのオブジェクト」なのか見分けがつきません。
型情報を見てみても・・・
VSCodeで型情報を見てみても、reactive()
で作ったオブジェクトであることは分かりませんでした。
公式にもref()
推奨らしい
公式ドキュメントにも「主にref()
を使うように」という旨が記載されていました。
reactive()
APIにはいくつかの制限があります(中略)
このような制約があるため、リアクティブな状態を宣言するための主要な API として
ref()
を使用することを推奨します。
みなさん、基本的にref()
を使っていきましょう。
【おまけ】 実はref()
の中で、内部的にreactive()
が使われている
非プリミティブ値(オブジェクトなど)をref()
に渡した場合、内部的にreactive()
が使われます。
なので「reactive()
は完全に不要」ということにはなりません。
ただし、ユーザから使えるAPIとしては要らないかなって思いました。