2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【Vue3.3】Composableの基本からTipsまで

Last updated at Posted at 2024-11-01

はじめに

Vue3 よりコンポーサブルという新しい概念が生まれました
これは処理のコンポーネント化と同義で、ロジックを切り離して使いまわそう!という考え方です

概念は理解できた、サンプルコードも読めた!
ただ実践で使うと、値が変わらない!呼び出し方が分からない!と少々難しい

てことで整理しました

※ 各種 import は省略しています(自動インポートを設定済み)

Vue 3.3 以上

Composable の基本

よく useMouse が例に上がってますね

useMouse.ts
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,
  }
}
Sample.vue
<template>
  <div>{{ mouse }}</div>
</template>

<script setup lang="ts">
const mouse = useMouse()
</script>

Animation.gif

こんな感じで ref関数 , イベント まで Vue ファイルと全く同じように書けます
その中で public として使いたい値を return してあげる感じです
意外と簡単

  • 複数個所で使用する処理
  • ネイティブの eventListener を使いたいとき

は Composable を使うと良いです!

Composable の値の利用方法

useMouse のそれぞれの値にアクセスするには、dot記法か分割代入どちらかを使います
お好みで

Sample.vue
<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>

Animation.gif

私は mouse で prefix を付けるほうが好き

記載はしてませんが、関数でもなんでも return したものをオブジェクトのように呼び出すことが可能です

Computed の使い方 (toRef())

Composable 内で Computed を使う場合はちょっと注意
そのまま呼び出すと .value を付与しないといけません

そこで toRef() という関数を経由して渡します
これに入れたものは、自動的に ref 属性としてリアクティブに扱われます 便利

useMouse.ts
export const useMouse = () => {
  // ...

  // mouse の X座標 の半分の値
  const halfX = computed(() => x.value / 2)

  return {
    // (1)
    halfX,
    // (2)
    refHalfX: toRef(halfX),
  }
}
Sample.vue
<template>
  <div>(1) {{ mouse.halfX.value }}</div>
  <div>(2) {{ mouse.refHalfX }}</div>
</template>

<script setup lang="ts">
const mouse = useMouse()
</script>

Animation.gif

シンプル is ベスト

.value については調査不足かもしれません...
templateの ts 領域で上手く動かない...

外部の値を使って処理したい (MaybeRef, unref())

特定の値を使った Composable を作りたくなることも多い
ただそのまま引数に渡すと、値の変更を検知してくれません...
ref や computed のようなリアクティブな値を渡すと良いです

その際は MaybeRefunref() を利用すると上手くいきます

useMouse.ts
export const useMouse = (px: MaybeRef<string | undefined>) => {
  // ...

  // mouse の X座標 にデフォルト値を加えたもの
  const fixedX = computed(() => x.value + Number(unref(px) ?? 0))

  return {
    fixedX: toRef(fixedX),
  }
}
Sample.vue
<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>

Animation.gif

MaybeRef の値を取り出す際は toValue() を使う
内部的には T | Ref<T> => T みたいなことをやってくれてる
.value みたいなものと思えばOK

もしオプションみたいな固定の設定値を渡したいときは、object形式で渡すと良い
キーが補完されるのでコーディングしやすい

外部の値を使って処理したい!が何故か値がリアクティブに変わらない (MaybeRefOrGetter, toValue())

稀に Props 値を使った場合など、値がリアクティブにならないことがある
そういう時は Getter 式で当てはめればOK

合わせて MaybeRefOrGettertoValue() を利用する
これらは先の例の Getter 対応版

useMouse.ts
export const useMouse = (px: MaybeRefOrGetter<string | undefined>) => {
  // ...
}
Sample.vue
<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>
Parent.vue
<template>
  <TestComponent :px="`100`" />
</template>

Animation.gif

コード統一のために、常にこっちでも良いかもね

何かの実装時に props だと動かないんだと思ってたのに、
テストコードではそのまま動いた...

昨今のSPAはリアクティブになる条件が難しいね

Composable をそのまま Component に渡したい (ReturnType<typeof xxx>)

できるぞ
Composable を ReturnType に通すと、型情報が出てくるので利用する

※ 簡略化のため toMouse は一番最初のものに戻す

Sample.vue
<template>
  <div>{{ mouse }}</div>
</template>

<script setup lang="ts">
defineProps<{
  mouse: ReturnType<typeof useMouse>
}>()
</script>
Parent.vue
<template>
  <TestComponent :mouse="mouse" />
</template>

<script setup lang="ts">
const mouse = useMouse()
</script>

Animation.gif

何も変わらずに使える!
これができると、ロジックのカプセル化に興味がわく人もでるのではないか

Composable を inject して利用したい (InjectionKey<ReturnType<typeof xxx>>)

できるぞ
Props の応用と InjectionKey を使って型の鍵を作る
その鍵を使って provide - inject する

※ 簡略化のため toMouse は一番最初のものに戻す
※ inject の説明はとても長くなるので省きます

Sample.vue
<template>
  <div>{{ mouse }}</div>
</template>

<script setup lang="ts">
import { useMouseKey } from '@/Pages/Sample.vue'

const mouse = inject(useMouseKey)
</script>
Parent.vue
<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 の書き心地がとても良い!

理解を深めたい人は VueUse ライブラリを覗いてみるとよいかも
大体の事例は既に実装されてる

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?