概要
コンポーザブルでコンポーネントにv-bind
で設定する値をオブジェクトで返すときに、イベントリスナーも一緒に返したいと思った。
そんなとき、実装する方法が全然分からなかったので、自分なりに調べて実装してみた。
結論
プロパティのキーを on${大文字始まりのイベント名}
にすれば、イベントリスナーとして認識される。
v-onを使った実装
<button @click="console.log('click')"></button>
v-bindを使った実装
<button v-bind="{ onClick: () => console.log('click') }"></button>
レンダー関数とJSX
実は、この書き方はレンダー関数内でVNodeを作る時の書き方。
// イベントリスナーは onXxx として渡す必要があります h('div', { onClick: () => {} })
引用した部分では、h()
関数のpropsを設定する箇所にイベントリスナーを設定している。
この書き方を真似すれば、v-bind
で渡すオブジェクトでもイベントリスナーを設定できるのではないかと考えた。
※v-bindでも同じ書き方ができると明記されている公式の記事を見つけたわけではない。もし、その記述がある箇所をご存じだったら、教えていただきたい……
活用例
実用的かどうかわからないけど、コンポーザブルで返した関数を使ってダイアログの状態を操作できるようなプログラムを実装してみた。
ダイアログの開閉状態をv-modelで管理し、ダイアログに表示する内容にかかわる情報などをpropsで渡すことを想定している。
export const useDialog = <DataType extends object = {}>(): { dialog: Ref<boolean>, props: ComputedRef<PropsType<DataType>>, open: (dialogData: DataType) => void } => { const dialog = ref<boolean>(false); const _data = ref<DataType>(); const props = computed<PropsType<DataType>>((): PropsType<DataType>=> { return { data: toValue(_data), "modelValue": dialog.value, "onUpdate:modelValue": (value: boolean) => { console.log(value) dialog.value = value; if (!value) { _data.value = undefined } } } }) const open = (dialogData: DataType): void => { _data.value = dialogData; dialog.value = true; } return { dialog, props, open } }
<script lang="ts" setup> import {useDialog} from "@/composables/dialog"; import AppDialog from "@/components/app-dialog.vue"; const { props, open } = useDialog<{ title: string; body: string; }>(); </script> <template> <div> <app-dialog v-bind="props"></app-dialog> <!-- ダイアログ --> <button @click="open({ title: 'タイトル', body: '本文' })">開く</button> </div> </template>
最後に
レンダー関数やJSXでの書き方というのが、全然関係ないところで役に立つとは思わなかった。 レンダー関数やJSXでVNodeを作るやり方は、単純なイベントリスナー以外にも色々とある。 読んでいない人がいたら、意外なことを発見できるかもしれない。