はじめに
先日大変わかりやすいこちらの記事を拝見しました。
こちらの記事ではカウンターアプリをさまざまなパターンで実装しています。
- view, 状態, ロジックが混在している
- 状態とロジックを切り出す
- Providerパターンを使って状態を共有する
自分でもこの3パターンを書いて理解を深めようと思います。
環境
nuxt 3.9.3
vue 3.4.14
unocss 0.58.3
作るもの
シンプルなカウンターアプリを作成します。
初期値は0でプラスボタンをクリックすると1増えて、マイナスボタンをクリックすると1減ります。
view, 状態, ロジックが混在しているパターン
一つのファイルにview (<span>{{ count }}</span>
), 状態 (const count = ref<number>(0)
), ロジック (const increment = () => count.value++
)が混在していますね。
pages/index.vue
<template>
<div min-h="100vh" flex="~" items="center" justify="center">
<div flex="~ col" gap="8px" items="center">
<span>{{ count }}</span>
<div flex="~ row" items="center" gap="8px">
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const count = ref<number>(0)
const increment = () => count.value++
const decrement = () => count.value--
</script>
状態とロジックを切り出すパターン
状態とロジックはuseCounter
へ切り出しています。
composables/useCounter.ts
export function useCounter() {
const count = ref<number>(0)
const increment = () => count.value++
const decrement = () => count.value--
return {
count,
increment,
decrement
}
}
pages/index.vue
<template>
<div min-h="100vh" flex="~" items="center" justify="center">
<div flex="~ col" gap="8px" items="center">
<span>{{ count }}</span>
<div flex="~ row" items="center" gap="8px">
<button @click="increment">+</button>
<button @click="decrement">-</button>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const { count, increment, decrement } = useCounter();
</script>
Providerパターン
Providerパターンを使って表示用のコンポーネント, インクリメントボタン, デクリメントボタン, ロジック (useCounter) に分けます。
composables/useCounter.ts
import type { InjectionKey } from "vue"
export const counterKey: InjectionKey<Counter> = Symbol('counter')
export function useCounter() {
const count = ref<number>(0)
const increment = () => count.value++
const decrement = () => count.value--
return {
count,
increment,
decrement
}
}
export type Counter = ReturnType<typeof useCounter>
components/counterDisplay.vue
<template>
<div>
<span>{{ counter?.count }}</span>
</div>
</template>
<script setup lang="ts">
const counter = inject<Counter>(counterKey)
</script>
components/counterIncrement.vue
<template>
<div>
<button @click="counter?.increment">+</button>
</div>
</template>
<script setup lang="ts">
const counter = inject<Counter>(counterKey)
</script>
components/counterDecrement.vue
<template>
<div>
<button @click="counter?.decrement">-</button>
</div>
</template>
<script setup lang="ts">
const counter = inject<Counter>(counterKey)
</script>
pages/index.vue
<template>
<div min-h="100vh" flex="~" items="center" justify="center">
<div flex="~ col" gap="8px" items="center">
<CounterDisplay />
<div flex="~ row" items="center" gap="8px">
<CounterIncrement />
<CounterDecrement />
</div>
</div>
</div>
</template>
<script setup lang="ts">
provide(counterKey, useCounter())
</script>