はじめに
この記事は、Vue3のお作法だとかベストプラクティスだとかを気にする前に、「とりあえずVue3書いてみたいよね!」といった人がリアクティブなコードを書きたくなった時に、まず調べるであろう事柄をちょっとだけピックアップした記事になります。
1. フォームの入力内容を変更したら、即座に画面に反映されてほしい
例えばjQueryを使うと、以下のようなコードで実現できます。
$(() => {
// 特定のフォームが変化されたら
$('#input').change(() => {
// 値を取得し
const val = $(this).val();
// 反映させたい部分に渡す
$('#output').text(val);
});
});
v-model
を使うんでしょう?
「Vue 入力 反映」などと検索すると、v-model という単語をたくさん見かけると思います。多分こいつを使うのだろうなと見当を付けて調べ進めると、あるコンポーネントは次のような書き方になるでしょう。
<template>
<div id="main">
<input v-model="inputValue" type="text" />
<p>Current input: {{inputValue}}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'Main',
setup() {
return {
inputValue: ref(''),
};
},
});
</script>
<style>
</style>
input
タグを通して入力された内容は inputValue
変数に渡され、その下の p
タグにリアルタイムに反映されます。
良いですね。以上、終わり!
ちょっと待て。私はカスタムコンポーネントに渡したいんだ
テキストや日付、セレクトボックスなどの入力フォーム一つ一つをカスタムコンポーネントとして定義している場合、以下のようなコードを書いてしまうかもしれません。
<template>
<div id="sample-form">
<input v-model="value" type="text" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'SampleForm',
props: {
value: String,
},
});
</script>
<style>
</style>
<template>
<div id="main">
<SampleForm :value="inputValue" />
<p>Current input: {{inputValue}}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import SampleForm from '@/components/SampleForm.vue';
export default defineComponent({
name: 'Main',
components: {
SampleForm,
},
setup() {
return {
inputValue: ref(''),
};
},
});
</script>
<style>
</style>
SampleForm
に先ほどの inputValue
を渡し、propsで受け取りコンポーネント内の input
タグのv-modelとして渡しています。
しかしこれはエラーになります。
ERROR Failed to compile with 1 error 11:16:16 PM
error in ./src/components/SampleForm.vue
Module Error (from ./node_modules/eslint-loader/index.js):
/.../src/components/SampleForm.vue
3:21 error Unexpected mutation of "value" prop vue/no-mutating-props
✖ 1 problem (1 error, 0 warnings)
どうやらコンポーネントに変数(の可変参照)を渡して操作してもらうことはできないか、一筋縄ではいかないようです。
vue-composable
は良いですよ!
先ほどのコード、ちょっと手直しをしてみましょう。
<template>
<div id="sample-form">
- <input v-model="value" type="text" />
+ <input v-model="vmValue" type="text" />
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
+ import { useVModel } from 'vue-composable';
export default defineComponent({
name: 'SampleForm',
props: {
value: String,
},
+ setup (props) {
+ const vmValue = useVModel(props, 'value');
+ return {
+ vmValue,
+ }
+ }
});
</script>
<style>
</style>
<template>
<div id="main">
- <SampleForm :value="inputValue" />
+ <SampleForm v-model:value="inputValue" />
<p>Current input: {{inputValue}}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import SampleForm from '@/components/SampleForm.vue';
export default defineComponent({
name: 'Main',
components: {
SampleForm,
},
setup() {
return {
inputValue: ref(''),
};
},
});
</script>
<style>
</style>
カスタムコンポーネントに渡す際に v-model:${変数名}="${変数}"
と記述して、カスタムコンポーネントの方では useVModel
関数でラップすると、なんとコンポーネントから外の変数を操作できてしまいます!
例えばフォーム全体の入力値を保持するクラスのインスタンスを1つ用意して、各入力項目を表すコンポーネントに対し、対応するインスタンスのフィールドを1つ渡して入力を常に把握するといったことも可能です。
素晴らしい!
おまけ:こうなったら関数も渡したいね
カスタムコンポーネントがボタンやテキストエリアを持っていて、そのクリックや入力値変更といったイベントを受け取りたい時、次のように書くと良いです。
<template>
<div id="main">
<SampleForm v-model:value="inputValue" />
+ <MyButton @on-click="execute" />
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue';
import SampleForm from '@/components/SampleForm.vue';
+ import MyButton from '@/components/MyButton.vue';
export default defineComponent({
name: 'Main',
components: {
SampleForm,
+ MyButton,
},
setup() {
+ const inputValue = ref('');
+
+ const execute = () => {
+ console.log('execute');
+ console.log(`current input: ${inputValue.value}`);
+ };
+
return {
- inputValue: ref(''),
+ inputValue,
+ execute,
};
},
});
</script>
<style>
</style>
<template>
<div id="my-button">
<button @click="onClick">ボタン</button>
</div>
</template>
<script lang="ts">
import { defineComponent, SetupContext } from 'vue';
export default defineComponent({
name: 'SampleForm',
setup (_props, context: SetupContext) {
const onClick = () => {
context.emit('on-click');
};
return {
vmValue,
onClick,
}
}
});
</script>
<style>
</style>
コンポーネント呼び出しの際に @event-name
を記述してハンドラを渡し、コンポーネント側では context.emit('event-name')
で実行することが可能です。
汎用的に使えそうですね!
まとめ
-
vue-composable
はいいぞ - 関数も渡せるぞ
- 「とりあえず動かせる」レベル感で使っているので、もっと適切なやり方があるかもしれません。自己責任で