2年前に Vue.js を触ったときに computed で双方向バインディングを実装した覚えがあったけど、
完全に抜けてたし、Composition Api x TypeScript は初めてだったので、未来の自分のために備忘録を残します。
実際のコード
親コンポーネントでは,v-model
にリアクティブな値を渡す。
親コンポーネント
<template>
<div>
<TextForm v-model="value" />
</div>
</template>
<script lang="ts">
import {defineComponent, ref} from '@nuxtjs/composition-api';
export default defineComponent({
components: {
TextForm
},
setup() {
const value = ref(10);
return {value}
}
});
</script>
子コンポーネントでは、
- 親から
v-model
で渡された値は、props.value
で受け取れる。 -
setup()
の引数でemit
を指定する。 -
computed
で、value
のgetter/setter
を用意する。 -
setter
でemit
する。 -
computed
の値を<input>
のv-model
にわたす。
子コンポーネント
<template>
<label>
<input v-model="v">
</label>
</template>
<script lang="ts">
import {computed, defineComponent} from '@nuxtjs/composition-api';
export default defineComponent({
name: 'TextForm',
props: {
value: {
type: Number,
default: 0
},
},
setup(props, {emit}) {
const v = computed({
get: () => props.value,
set: (newValue) => {
emit('input', newValue);
}
});
return {v};
}
});
</script>
ちなみに、今回 value
の型が number
で、setter
の引数も number
として推論されるのですが、
フォームを一度空にしたり、マイナスの値を入力しようとすると、一時的に string
型が渡されエラーを吐きます。
なので、以下の通り型指定をして、対応しました。
子コンポーネント
<script lang="ts">
... 中略 ...
setup(props, {emit}) {
const v = computed({
get: () => props.value,
set: (newValue: number | '' | '-' | '.') => {
const number = parseFloat(String(newValue));
if (!Number.isNaN(number)) {
emit('input', number);
}
}
});
... 中略 ...
</script>