はじめに
Vue3 よりコンポーサブルという新しい概念が生まれました
これは処理のコンポーネント化と同義で、ロジックを切り離して使いまわそう!という考え方です
概念は理解できた、サンプルコードも読めた!
ただ実践で使うと、値が変わらない!呼び出し方が分からない!と少々難しい
てことで整理しました
※ 各種 import は省略しています(自動インポートを設定済み)
Vue 3.3 以上
Composable の基本
よく useMouse
が例に上がってますね
export const useMouse = () => {
const x = ref<number>(0)
const y = ref<number>(0)
const update = (event: MouseEvent) => {
x.value = event.pageX
y.value = event.pageY
}
onMounted(() => window.addEventListener('mousemove', update))
onUnmounted(() => window.removeEventListener('mousemove', update))
return {
x,
y,
}
}
<template>
<div>{{ mouse }}</div>
</template>
<script setup lang="ts">
const mouse = useMouse()
</script>
こんな感じで ref
や 関数
, イベント
まで Vue ファイルと全く同じように書けます
その中で public として使いたい値を return してあげる感じです
意外と簡単
- 複数個所で使用する処理
- ネイティブの eventListener を使いたいとき
は Composable を使うと良いです!
Composable の値の利用方法
useMouse
のそれぞれの値にアクセスするには、dot記法か分割代入どちらかを使います
お好みで
<template>
<div>(1) {{ mouse.x }}, {{ mouse.y }}</div>
<div>(2) {{ x }}, {{ y }}</div>
</template>
<script setup lang="ts">
// (1)
const mouse = useMouse()
// (2)
const { x, y } = useMouse()
</script>
私は mouse
で prefix を付けるほうが好き
記載はしてませんが、関数でもなんでも return したものをオブジェクトのように呼び出すことが可能です
Computed の使い方 (toRef()
)
Composable 内で Computed
を使う場合はちょっと注意
そのまま呼び出すと .value
を付与しないといけません
そこで toRef()
という関数を経由して渡します
これに入れたものは、自動的に ref 属性としてリアクティブに扱われます 便利
export const useMouse = () => {
// ...
// mouse の X座標 の半分の値
const halfX = computed(() => x.value / 2)
return {
// (1)
halfX,
// (2)
refHalfX: toRef(halfX),
}
}
<template>
<div>(1) {{ mouse.halfX.value }}</div>
<div>(2) {{ mouse.refHalfX }}</div>
</template>
<script setup lang="ts">
const mouse = useMouse()
</script>
シンプル is ベスト
.value については調査不足かもしれません...
templateの ts 領域で上手く動かない...
外部の値を使って処理したい (MaybeRef
, unref()
)
特定の値を使った Composable を作りたくなることも多い
ただそのまま引数に渡すと、値の変更を検知してくれません...
ref や computed のようなリアクティブな値を渡すと良いです
その際は MaybeRef
と unref()
を利用すると上手くいきます
export const useMouse = (px: MaybeRef<string | undefined>) => {
// ...
// mouse の X座標 にデフォルト値を加えたもの
const fixedX = computed(() => x.value + Number(unref(px) ?? 0))
return {
fixedX: toRef(fixedX),
}
}
<template>
<input v-model="px" size="small" />
<div>{{ mouse.fixedX }}</div>
</template>
<script setup lang="ts">
const px = ref<string>()
const mouse = useMouse(px)
</script>
型 MaybeRef
の値を取り出す際は toValue()
を使う
内部的には T | Ref<T>
=> T
みたいなことをやってくれてる
.value
みたいなものと思えばOK
もしオプションみたいな固定の設定値を渡したいときは、object形式で渡すと良い
キーが補完されるのでコーディングしやすい
外部の値を使って処理したい!が何故か値がリアクティブに変わらない (MaybeRefOrGetter
, toValue()
)
稀に Props 値を使った場合など、値がリアクティブにならないことがある
そういう時は Getter 式で当てはめればOK
合わせて MaybeRefOrGetter
と toValue()
を利用する
これらは先の例の Getter
対応版
export const useMouse = (px: MaybeRefOrGetter<string | undefined>) => {
// ...
}
<template>
<div>px: {{ px }}</div>
<div>{{ mouse.fixedX }}</div>
</template>
<script setup lang="ts">
const props = defineProps<{
px?: string
}>()
const mouse = useMouse(() => props.px)
</script>
<template>
<TestComponent :px="`100`" />
</template>
コード統一のために、常にこっちでも良いかもね
何かの実装時に props だと動かないんだと思ってたのに、
テストコードではそのまま動いた...昨今のSPAはリアクティブになる条件が難しいね
Composable をそのまま Component に渡したい (ReturnType<typeof xxx>
)
できるぞ
Composable を ReturnType
に通すと、型情報が出てくるので利用する
※ 簡略化のため toMouse
は一番最初のものに戻す
<template>
<div>{{ mouse }}</div>
</template>
<script setup lang="ts">
defineProps<{
mouse: ReturnType<typeof useMouse>
}>()
</script>
<template>
<TestComponent :mouse="mouse" />
</template>
<script setup lang="ts">
const mouse = useMouse()
</script>
何も変わらずに使える!
これができると、ロジックのカプセル化に興味がわく人もでるのではないか
Composable を inject して利用したい (InjectionKey<ReturnType<typeof xxx>>
)
できるぞ
Props の応用と InjectionKey
を使って型の鍵を作る
その鍵を使って provide - inject する
※ 簡略化のため toMouse
は一番最初のものに戻す
※ inject の説明はとても長くなるので省きます
<template>
<div>{{ mouse }}</div>
</template>
<script setup lang="ts">
import { useMouseKey } from '@/Pages/Sample.vue'
const mouse = inject(useMouseKey)
</script>
<template>
<TestComponent />
</template>
<script lang="ts">
export const useMouseKey: InjectionKey<ReturnType<typeof useMouse>> = Symbol()
</script>
<script setup lang="ts">
const mouse = useMouse()
provide(useMouseKey, mouse)
</script>
キーを使うことで型情報も引っ張ってこれる
script setup でも export できればもう少し簡単に表現できるんだけどね...
inject は革命が起きる一方、global に定義しているようなものなのでスパゲッティになりやすい...
また undefined が有り得るので、気を付けてコーデイングをしよう
おわりに
Vue3 ってこんなに進化してるの!
これだけできれば、バケツリレーの廃止とか、通信ロジックの隠匿とかできそうじゃない?
個人的にはまだ shallowRef
とかの使い方が分かってない
とりあえず「リアクティブに反映させたい値は Ref 化する」だけ覚えれば何とかなる
React も嫌いじゃないけど、私は Vue を信仰していくぅ
Vue の書き心地がとても良い!
- リアクティビティー API: コア | Vue.js
- リアクティビティー API: ユーティリティー | Vue.js
- リアクティビティー API: 上級編 | Vue.js
- ユーティリティー型 | Vue.js
理解を深めたい人は VueUse
ライブラリを覗いてみるとよいかも
大体の事例は既に実装されてる