おはようございます!そうすけです。
運営者:そうすけ
30代のブロガーとエンジニア。
工場勤務→情シス→ソフトウェアエンジニア
主にフロントエンド開発を行っています。
趣味はガジェットと植物。
VUEはコンポーネントを使いまわしできる部分が特徴です。
そして双方向バインディングも特徴。
ですが、親子コンポーネント間で双方向バインディングの組み合わせはどうでしょうか。
例えば入力フォームです。inputタグで入力してもらって登録などを実装する場合。
コンポーネントを使わず親のみで行う場合、HTMLのinputタグであればv-modelで双方向バインディングできます。
<template>
<p>{{textInput}}<p>
<input v-model="textInput">
</template>
<script>
const textInput = ref<string|null>(null)
</script>
これがinputタグを含んだコンポーネントの場合だと難しいです。
入力データを親側に返したいですが、結論、v-modelのみでは一工夫ないとできません。
なぜなら、どの値をどのようにやりとりするか、コンポーネント間になった場合は明確でないからです。
また、コンポーネント間の値のリアクティブなやりとりはpropsとemitsが必要です。
実際は入力コンポーネントを使いまわすとデザインの統一感も出ますし、開発することが多く効率も良いです。
なので、computed、props、emits、v-modelを組み合わせて使用します。
computedとは?
Refで定義した値を更新があるたびに再計算とキャッシュをしてくれる機能 です。
算出プロパティという名前です。
変数を監視する役割を持っていて、キャッシュを持ちます。
例えば、下記のプログラムです。ボタンを押すとincrementが実行され、refで定義したcountの値が変わりますが、doubleの値は描画されません。
<script setup lang ="ts">
import { ref } from "vue"
const count = ref( 0)
const increment = () => count. value ++
const double = count. value * 2 </ script >
< template >
<button type =" button" @click =" increment">
count is {{ count }}
</ button >
< div > count * 2 is {{ double }} </ div >
</ template >
ref単体を双方向バインディングしているモノに関しては計算値が表示できるのですが、スクリプト内で計算した変数自体は再描画されません。
ですが、doubleで行う処理をcomputedでラップしてあげると、doubleに計算結果が描画されます。
const double =computed(()=>
count.value * 2
)
}
つまり、computed内で監視しているrefで定義した変数が変化すると、computedの戻り値が再計算され、値を取得するようになる機能です。
厳密にはcompoutedの戻り値がcomputedRefというRefを継承した値なので、double自身もrefオブジェクトなので値が変更されたら、再描画されます。
computedが依存するref自体の値が変わらない限り再計算されないです。
下記がポイントです。
- refをベースにscript内で算出したいならcomputedで関数をラップする。
- computedは算出した値をキャッシュする。
getter関数とsetter関数
通常computedは、refで定義した値が変動するたびに再計算して、値を取得するのみです。
(getterがここになります)
computedで計算された変数の戻り値が更新されたときの処理を書くのがSetterになります。
変数の戻り値が更新されたときというのは、getterに指定した関数が再計算されるタイミングです。
- refで定義した変数が変化する
- refはcomputedで監視してるため、再計算が発動し戻り値が変更される(getter)
- 変数の戻り値が更新されたので、setterに定めたときの処理が発火する
v-modelとの組み合わせで親子間のやり取りがリアクティブに
以上を踏まえて、V-modelを用いてコンポーネント間のやり取りをします。
親コンポーネントでやること
- 親要素でやりとりしたい値をref型で定義
- <コンポーネント名 v-model=●●>とする
子コンポーネントでやること
- 親子でやりとりしたい値を子にProps・Emitsで定義
- 子コンポーネントでcomoputedを使用する
- Getにはpropsで指定した値を演算し返却
- Setには返却するときに起こす親のイベントを定義する
<template>
<div>
<label>タイトル
<InputText v-model="name"></InputText>
</label>
<label>
調理方法
<InputText v-model="form"></InputText>
</label>
<div>
{{ "親name:"+name+" 親form:"+form }}
</div>
</div>
</template>
<script setup lang="ts">
const name = ref<string|null>(null);
const form = ref<string|null>(null);
</script>
<template>
<div>
<input type="text"
v-model="modelValue">
</div>
<div>
{{ "子"+modelValue }}
</div>
</template>
<script setup lang="ts">
//親からうけとるPropsを用意
const props = defineProps<{
modelValue:string|null
}>()
//親で実行するイベントとイベント引数をemitsで用意
const emits = defineEmits<{
"update:modelValue":[value:string|null]
}>()
//inputタグのmodelValueに入力される値を監視
const modelValue = computed({
get:()=> props.modelValue,
set:(value)=> emits('update:modelValue',value)
})
</script>
文字を書く前をこんな感じです。
文字を書くと親にも子にもリアクティブに帰ってきているのがわかります。
理屈
v-modelは分解するとmodelValueという名前のプロパティを渡して、
変化があった時にupdate:modelValueというイベントを発火しているのと同じです。
propsとemitになっています。
それをtemplateとscriptでやりとりすることで双方向バインディングを可能にしている。
まとめ
親子間コンポーネントの双方向バインディングをまとめると以下の流れです。
- 親でrefを定義する
- 親で子コンポーネントにv-modelを用意
- 子コンポーネントのv-model=modelValueを記載
- 子コンポーネントのcomputedのgetterとsetterでpropsとemitsを記載する
コメント