Vue 3 ã³ã³ããŒã¶ãã«äžçŽã¬ã€ãïŒåå©çšå¯èœãªãã¿ãŒã³ãšæè¡é¢æ¥ã®è³ªå 2026
Vue 3ã®é«åºŠãªã³ã³ããŒã¶ãã«ãã¿ãŒã³ãç¶²çŸ çã«è§£èª¬ãéåæåŠçãäŸåæ§æ³šå ¥ããã©ãŒã ããªããŒã·ã§ã³ããã¹ãææ³ã2026å¹Žã®æè¡é¢æ¥ã§é »åºãã質åãšåçã玹ä»ããŸãã

ã³ã³ããŒã¶ãã«ã¯ãVue 3ã«ãããããžãã¯åå©çšã®æ ¹å¹¹ãæãä»çµã¿ã§ããComposition APIã®æ®åãé²ãã çŸåšãå
ç¢ã§ã¿ã€ãã»ãŒããã€ãã¹ãå¯èœãªã³ã³ããŒã¶ãã«ãèšèšããèœåã¯ãæè¡é¢æ¥ã«ãããŠã·ãã¢ã¬ãã«ã®éçºè
ãšäžçŽè
ãæç¢ºã«åºå¥ããææšãšãªã£ãŠããŸãã2026幎ã«ãããŠã¯ãåãªãrefãcomputedã®äœ¿ãæ¹ã«ãšã©ãŸãããã©ã€ããµã€ã¯ã«ã®ç²Ÿå¯ãªç®¡çããªã¢ã¯ãã£ã颿°ã®åæããããŠè€éãªã¢ããªã±ãŒã·ã§ã³ã®ã¢ãžã¥ã©ãŒã¢ãŒããã¯ãã£ãŸã§ãæ±ããããŸãã
æ¬èšäºã§ã¯ãVue 3ã®ã³ã³ããŒã¶ãã«ã«ãããäžçŽãã¿ãŒã³ãäœç³»çã«è§£èª¬ããŸããé©åã«æ§é åãããã³ã³ããŒã¶ãã«ã®èšèšååãããéåæåŠçãäŸåæ§æ³šå ¥ããã¹ãæŠç¥ã«è³ããŸã§ãæ¬çªç°å¢ããã³æè¡é¢æ¥ã®äž¡æ¹ã§å³åº§ã«æŽ»çšã§ããå ·äœçãªå®è£ äŸãæç€ºããŸãã
ã³ã³ããŒã¶ãã«ãšã¯ãVueã®Composition APIãæŽ»çšããŠã¹ããŒããã«ãªããžãã¯ãã«ãã»ã«åãåå©çšããããã®é¢æ°ã§ããæ
£äŸãšããŠuseãã¬ãã£ãã¯ã¹ãä»ããããŸãïŒäŸïŒuseCounterãuseFetchDataïŒãVue 2ã®ããã¯ã¹ã€ã³ãšã¯ç°ãªããã³ã³ããŒã¶ãã«ã¯æç€ºçãªåä»ããååè¡çªã®åé¿ãäŸåé¢ä¿ã®éææ§ãæäŸããŸãã
é©åã«æ§é åãããã³ã³ããŒã¶ãã«ã®èšèšåå
æ¬çªå質ã®ã³ã³ããŒã¶ãã«ã¯ãããã€ãã®ã¢ãŒããã¯ãã£ååã«åŸããŸããå
·äœçã«ã¯ãå
¥åºåã®å³å¯ãªåå®çŸ©ã責åã®åé¢ãäºæž¬å¯èœãªã€ã³ã¿ãŒãã§ãŒã¹ã§ãã以äžã®useCounterã³ã³ããŒã¶ãã«ã¯ããããã®åºæ¬èŠçŽã瀺ããŠããŸãã
import { ref, computed, type Ref } from 'vue'
interface UseCounterOptions {
min?: number
max?: number
initialValue?: number
}
interface UseCounterReturn {
count: Ref<number>
doubled: Ref<number>
increment: () => void
decrement: () => void
reset: () => void
}
export function useCounter(options: UseCounterOptions = {}): UseCounterReturn {
const { min = 0, max = Infinity, initialValue = 0 } = options
const count = ref(initialValue)
const doubled = computed(() => count.value * 2)
function increment() {
if (count.value < max) count.value++
}
function decrement() {
if (count.value > min) count.value--
}
function reset() {
count.value = initialValue
}
return { count, doubled, increment, decrement, reset }
}ãã®å®è£
ã«ã¯ããã€ãã®æ³šç®ãã¹ãèšèšèŠçŽ ããããŸããUseCounterOptionsã€ã³ã¿ãŒãã§ãŒã¹ã¯èšå®é
ç®ã«å¯Ÿããæç¢ºãªå¥çŽãå®çŸ©ããUseCounterReturnã¯ã³ã³ããŒã¶ãã«ãå
¬éããå
å®¹ãæ£ç¢ºã«ããã¥ã¡ã³ãåããŠããŸãã颿°æ¬äœã§ã®ããã©ã«ãå€ä»ããã¹ãã©ã¯ãã£ãªã³ã°ã«ãããæç€ºçãªèšå®ããªããŠãäºæž¬å¯èœãªåäœãä¿èšŒãããŸãã
ååä»ããªããžã§ã¯ãïŒé åã§ã¯ãªãïŒãè¿ãããšã§ãåŒã³åºãåŽã¯ãã¹ãã©ã¯ãã£ãªã³ã°ã«ãã£ãŠå¿ èŠãªããããã£ã®ã¿ãéžæã§ããŸãããã®èŠçŽã¯Vueãšã³ã·ã¹ãã å šäœã§æ¡çšãããŠãããåŒã³åºãå ã³ãŒãã®å¯èªæ§ãšä¿å®æ§ãåäžãããŸãã
minãšmaxã®å¢çå€ã¯ãæ¬çªç°å¢ã®ã³ã³ããŒã¶ãã«ã§é »åºãããã¿ãŒã³ã瀺ããŠããŸããããžãã¹ããžãã¯ã®å¶çŽããªã¢ã¯ãã£ãããžãã¯å
ã§çŽæ¥æ€èšŒããããšã§ãã³ã³ã·ã¥ãŒããŒã³ã³ããŒãã³ãã«ã«ãŒã«ã忣ããã®ã鲿¢ããŸãã
éåæã³ã³ããŒã¶ãã«ãšãšã©ãŒãã³ããªã³ã°
HTTPãªã¯ãšã¹ãã®ç®¡çã¯ãã³ã³ããŒã¶ãã«ã®æãäžè¬çãªãŠãŒã¹ã±ãŒã¹ã®äžã€ã§ããé©åã«èšèšãããéåæã³ã³ããŒã¶ãã«ã¯ããªã¯ãšã¹ãã®å
šã©ã€ããµã€ã¯ã«ã管çããå¿
èŠããããŸããããªãã¡ãããŒãã£ã³ã°ç¶æ
ãæåããšã©ãŒããããŠãã£ã³ã»ã«ã§ãã以äžã®ãã¿ãŒã³ã¯ãèªåçãªãªã¢ã¯ãã£ããã£ã®ããã«watchEffectãããªã¯ãšã¹ãã®é©åãªãã£ã³ã»ã«ã®ããã«AbortControllerãçµ±åããŠããŸãã
import { ref, watchEffect, onUnmounted, toValue, type Ref, type MaybeRefOrGetter } from 'vue'
interface UseFetchReturn<T> {
data: Ref<T | null>
error: Ref<string | null>
isLoading: Ref<boolean>
refresh: () => Promise<void>
}
export function useFetchData<T>(
url: MaybeRefOrGetter<string>
): UseFetchReturn<T> {
const data = ref<T | null>(null) as Ref<T | null>
const error = ref<string | null>(null)
const isLoading = ref(false)
let abortController: AbortController | null = null
async function fetchData() {
// Cancel any in-flight request
abortController?.abort()
abortController = new AbortController()
isLoading.value = true
error.value = null
try {
const response = await fetch(toValue(url), {
signal: abortController.signal
})
if (!response.ok) throw new Error(`HTTP ${response.status}`)
data.value = await response.json()
} catch (err) {
if (err instanceof DOMException && err.name === 'AbortError') return
error.value = err instanceof Error ? err.message : 'Unknown error'
} finally {
isLoading.value = false
}
}
// Re-fetch when URL changes reactively
watchEffect(() => {
fetchData()
})
onUnmounted(() => abortController?.abort())
return { data, error, isLoading, refresh: fetchData }
}urlãã©ã¡ãŒã¿ã«MaybeRefOrGetter<string>ã䜿çšããããšã§ãæå€§éã®æè»æ§ã確ä¿ãããŸããåŒã³åºãåŽã¯éçãªæååãrefããŸãã¯ã²ãã¿ãŒé¢æ°ã®ããããæž¡ãããšãã§ããŸããtoValue()颿°ã¯ãçã®å€ã§ãã£ãŠããªã¢ã¯ãã£ããªå€ã§ãã£ãŠããåºåºã®åãèªåçã«è§£æ±ºããŸãã
AbortControllerã«ãããã£ã³ã»ã«æ©æ§ã¯ãåã®ãªã¯ãšã¹ããå®äºããåã«æ°ãããªã¯ãšã¹ããçºè¡ãããéã®ç«¶åç¶æ
ïŒã¬ãŒã¹ã³ã³ãã£ã·ã§ã³ïŒã鲿¢ããŸããonUnmountedããã¯ã¯ã³ã³ããŒãã³ãç Žæ£æã«é²è¡äžã®ãªã¯ãšã¹ãã確å®ã«ã¯ãªãŒã³ã¢ããããã¡ã¢ãªãªãŒã¯ãã¢ã³ããŠã³ãæžã¿ã³ã³ããŒãã³ããžã®ç¶æ
æŽæ°ãåé¿ããŸãã
æ»ãå€ãšããŠå
¬éãããrefreshã¡ãœããã«ãããã³ã³ã·ã¥ãŒããŒã³ã³ããŒãã³ãã¯ããŒã¿ã®åèªã¿èŸŒã¿ãæåã§ããªã¬ãŒã§ããŸãããæŽæ°ããã¿ã³ã®ãããªãŠãŒã¶ãŒã¢ã¯ã·ã§ã³ã«ã¯äžå¯æ¬ ãªãã¿ãŒã³ã§ãã
ã©ã€ããµã€ã¯ã«ããã¯ïŒonMountedãonUnmountedãªã©ïŒã¯ãã³ã³ããŒã¶ãã«æ¬äœã§åæçã«åŒã³åºãå¿
èŠããããŸããéåæã³ãŒã«ããã¯ãsetTimeoutå
ã§åŒã³åºããŠã¯ãªããŸãããVueã¯ãããã®ããã¯ãåŒã³åºãæç¹ã§ã¢ã¯ãã£ããªã³ã³ããŒãã³ãã€ã³ã¹ã¿ã³ã¹ã«é¢é£ä»ããŸããé
å»¶åŒã³åºãã¯ãµã€ã¬ã³ããšã©ãŒãåŒãèµ·ããããã誀ã£ãã³ã³ããŒãã³ãã«ããã¯ãçŽä»ãããããå¯èœæ§ããããŸãã
ã³ã³ããŒã¶ãã«ã®åæ
ã³ã³ããŒã¶ãã«ã®çã®åã¯ãçžäºã«çµã¿åãããèœåã«ãããŸããäžäœã¬ãã«ã®ã³ã³ããŒã¶ãã«ãè€æ°ã®ç¹åããã³ã³ããŒã¶ãã«ãçµ±åãã責åã®æç¢ºãªåé¢ãç¶æããªããè€éãªæ©èœãæ§ç¯ã§ããŸãã以äžã®ãã¿ãŒã³ã¯ããããŠã³ã¹ä»ãæ€çŽ¢ãããŒãžããŒã·ã§ã³ãããŒã¿ååŸãçµã¿åãããŠããŸãã
import { ref, computed, watch, type Ref } from 'vue'
import { useFetchData } from './useFetchData'
import { useDebouncedRef } from './useDebouncedRef'
interface UsePaginatedSearchReturn<T> {
query: Ref<string>
page: Ref<number>
results: Ref<T[] | null>
totalPages: Ref<number>
isLoading: Ref<boolean>
error: Ref<string | null>
nextPage: () => void
prevPage: () => void
}
export function usePaginatedSearch<T>(
baseUrl: string,
perPage = 20
): UsePaginatedSearchReturn<T> {
const query = useDebouncedRef('', 300)
const page = ref(1)
const totalPages = ref(1)
const apiUrl = computed(
() => `${baseUrl}?q=${encodeURIComponent(query.value)}&page=${page.value}&limit=${perPage}`
)
const { data, error, isLoading } = useFetchData<{ items: T[]; total: number }>(apiUrl)
const results = computed(() => data.value?.items ?? null)
watch(data, (response) => {
if (response) {
totalPages.value = Math.ceil(response.total / perPage)
}
})
// Reset to page 1 when query changes
watch(query, () => { page.value = 1 })
function nextPage() {
if (page.value < totalPages.value) page.value++
}
function prevPage() {
if (page.value > 1) page.value--
}
return { query, page, results, totalPages, isLoading, error, nextPage, prevPage }
}ãã®ã³ã³ããŒã¶ãã«ã¯ãããã€ãã®é«åºŠãªåæãã¯ããã¯ãå®èšŒããŠããŸããuseDebouncedRefã¯ã¿ã€ãã³ã°å¶åŸ¡ããžãã¯ãã«ãã»ã«åããããŒã¹ãããŒã¯ããšã«APIãžéå°ãªãªã¯ãšã¹ããéä¿¡ãããã®ãé²ããŸããcomputedã®apiUrlã¯ã¯ãšãªãŸãã¯ããŒãžã倿Žããããšèªåçã«URLãåæ§ç¯ããuseFetchDataãéããŠæ°ããªããŒã¿ååŸãããªã¬ãŒããŸãã
queryã«å¯Ÿããwatchã¯ãæ°ããæ€çŽ¢ã®ãã³ã«ããŒãžã1ã«ãªã»ããããŸãããŠãŒã¶ãŒãæåŸ
ããåäœã§ãããªãããçŽ æŽãªå®è£
ã§ã¯èŠèœãšãããã¡ãªãã€ã³ãã§ãããã®è©³çްã¯ãããžãã¯ãã³ã³ããŒãã³ãã«åæ£ãããã®ã§ã¯ãªããã³ã³ããŒã¶ãã«ã«ã«ãã»ã«åããæçŸ©ã瀺ããŠããŸãã
ã³ã³ããŒã¶ãã«ã®åæã¯åäžè²¬ä»»ã®ååã«åŸããŸããuseDebouncedRefã¯ãããŠã³ã¹ã管çããuseFetchDataã¯ãªã¯ãšã¹ãã»ã¬ã¹ãã³ã¹ã®ãµã€ã¯ã«ã管çããusePaginatedSearchãå
šäœãçµ±æ¬ããŸãããã®ã¢ãŒããã¯ãã£ã«ãããåã¬ã€ã€ãŒã®åäœãã¹ããç¬ç«ããŠå®è¡ã§ããŸãã
Vue.js / Nuxt.jsã®é¢æ¥å¯Ÿçã¯ã§ããŠããŸããïŒ
ã€ã³ã¿ã©ã¯ãã£ããªã·ãã¥ã¬ãŒã¿ãŒãflashcardsãæè¡ãã¹ãã§ç·Žç¿ããŸãããã
provide/injectã«ããäŸåæ§æ³šå ¥
ã³ã³ããŒãã³ãããªãŒå
ã§é¢ããäœçœ®ã«ããã³ã³ããŒãã³ãéã§ç¶æ
ãå
±æããå ŽåãVueã®provide/injectã·ã¹ãã ã¯propsã®ãã±ããªã¬ãŒïŒprop drillingïŒã«å¯Ÿãããšã¬ã¬ã³ããªä»£æ¿ææ®µãæäŸããŸããã³ã³ããŒã¶ãã«ã¯ãã®ä»çµã¿ãã«ãã»ã«åããã¯ãªãŒã³ã§ã¿ã€ãã»ãŒããªAPIãæäŸã§ããŸãã
import { provide, inject, ref, readonly, type InjectionKey, type Ref } from 'vue'
type Theme = 'light' | 'dark' | 'system'
interface ThemeContext {
theme: Readonly<Ref<Theme>>
setTheme: (t: Theme) => void
resolvedTheme: Readonly<Ref<'light' | 'dark'>>
}
const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme')
export function provideTheme(initial: Theme = 'system') {
const theme = ref<Theme>(initial)
const resolvedTheme = computed<'light' | 'dark'>(() => {
if (theme.value !== 'system') return theme.value
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light'
})
function setTheme(t: Theme) {
theme.value = t
}
const context: ThemeContext = {
theme: readonly(theme),
setTheme,
resolvedTheme: readonly(resolvedTheme)
}
provide(ThemeKey, context)
return context
}
export function useTheme(): ThemeContext {
const context = inject(ThemeKey)
if (!context) {
throw new Error('useTheme() requires a parent component to call provideTheme()')
}
return context
}Symbolãçšããåä»ãInjectionKeyã¯ãã€ã³ãžã§ã¯ã·ã§ã³ããŒã®äžææ§ãä¿èšŒããinjectåŒã³åºãæã«èªåçãªåæšè«ãæäŸããŸãããã®ãã¿ãŒã³ã¯ãæååããŒã¹ã®ããŒã«ååšããããŒè¡çªã®ãªã¹ã¯ãæé€ããŸãã
provideThemeãšuseThemeã®åé¢ã¯ãæç¢ºãªã¢ãŒããã¯ãã£å¢çã確ç«ããŸããã«ãŒãã³ã³ããŒãã³ãïŒãŸãã¯ã¬ã€ã¢ãŠãïŒãprovideThemeãåŒã³åºããŠã³ã³ããã¹ããåæåããåå«ã³ã³ããŒãã³ããuseThemeãåŒã³åºããŠæ¶è²»ããŸããå
¬éãããrefã«readonlyãé©çšããããšã§ãã³ã³ã·ã¥ãŒããŒããã®æå³ããªããã¥ãŒããŒã·ã§ã³ã鲿¢ããŸãã
resolvedThemeã®computedã¯é »åºãã¿ãŒã³ã瀺ããŠããŸããæœè±¡çãªèšå®å€ïŒ'system'ïŒãã·ã¹ãã ç°å¢èšå®ã«åºã¥ãå
·äœçãªå€ïŒ'light'ãŸãã¯'dark'ïŒã«å€æãããã®ã§ãããã®æœè±¡åã¬ãã«ã«ãããã³ã³ã·ã¥ãŒããŒã³ã³ããŒãã³ãã®ããžãã¯ã倧å¹
ã«ç°¡çŽ åãããŸãã
ãã©ãŒã ããªããŒã·ã§ã³çšã³ã³ããŒã¶ãã«
ãã©ãŒã ããªããŒã·ã§ã³ã¯ãã³ã³ããŒã¶ãã«ãžã®ã«ãã»ã«åãã倧ããªæ©æµãåããè€éãªãŠãŒã¹ã±ãŒã¹ã§ãã以äžã®ãã¿ãŒã³ã¯ãã«ãŒã«ããŒã¹ã®å®£èšçããªããŒã·ã§ã³ããšã©ãŒã®ãªã¢ã¯ãã£ã管çãã°ããŒãã«ãªæå¹æ§ç¶æ ãæäŸããŸãã
import { reactive, computed, type UnwrapNestedRefs } from 'vue'
type ValidationRule<T> = (value: T) => string | true
type FieldRules<T> = { [K in keyof T]?: ValidationRule<T[K]>[] }
interface UseFormReturn<T extends Record<string, any>> {
fields: UnwrapNestedRefs<T>
errors: Record<keyof T, string>
isValid: Ref<boolean>
validate: () => boolean
resetErrors: () => void
}
export function useFormValidation<T extends Record<string, any>>(
initialValues: T,
rules: FieldRules<T>
): UseFormReturn<T> {
const fields = reactive({ ...initialValues }) as UnwrapNestedRefs<T>
const errors = reactive(
Object.keys(initialValues).reduce(
(acc, key) => ({ ...acc, [key]: '' }),
{} as Record<keyof T, string>
)
)
function validate(): boolean {
let valid = true
for (const key of Object.keys(rules) as (keyof T)[]) {
const fieldRules = rules[key] || []
errors[key] = '' as any
for (const rule of fieldRules) {
const result = rule(fields[key])
if (result !== true) {
errors[key] = result as any
valid = false
break // Stop at first error per field
}
}
}
return valid
}
function resetErrors() {
for (const key of Object.keys(errors)) {
(errors as any)[key] = ''
}
}
const isValid = computed(() =>
Object.values(errors).every((e) => e === '')
)
return { fields, errors, isValid, validate, resetErrors }
}ãã®ã³ã³ããŒã¶ãã«ã¯fieldsãªããžã§ã¯ãã«refã§ã¯ãªãreactiveã䜿çšããŠããŸããããã«ãã.valueãªãã§ããããã£ã«çŽæ¥ã¢ã¯ã»ã¹ã§ããŸããããªããŒã·ã§ã³ã«ãŒã«ã¯ãæåæã«trueãã倱ææã«ãšã©ãŒã¡ãã»ãŒãžã®æååãè¿ããšããç°¡æœãªèŠçŽã«åŸããŸããbreakæã«ãããã£ãŒã«ãããšã«æåã®ãšã©ãŒã§ããªããŒã·ã§ã³ã忢ãããšã©ãŒã¡ãã»ãŒãžã®èç©ãé²ããŸãã
FieldRules<T>åã¯TypeScriptã®ãããåãæŽ»çšããã«ãŒã«ããã©ãŒã ã®ãã£ãŒã«ãã«ç¢ºå®ã«å¯Ÿå¿ããããšãä¿èšŒããŸãããã®ã¢ãããŒãã¯ã³ã³ãã€ã«æã«åãšã©ãŒãæ€åºããæ¬çªç°å¢ã®ãã°ãåæžããŸãã
isValidã®computedã¯ããã©ãŒã å
šäœã®ç¶æ
ã瀺ããªã¢ã¯ãã£ããªã€ã³ãžã±ãŒã¿ãæäŸããŸããéä¿¡ãã¿ã³ã®æå¹ã»ç¡å¹ã®åãæ¿ãã«ãã³ã³ããŒãã³ãåŽã§è¿œå ã®ããžãã¯ãå¿
èŠãšããŸããã
ã³ã³ããŒã¶ãã«ã®ãã¹ã
ã³ã³ããŒã¶ãã«ã¯ãªã¢ã¯ãã£ããã£ã·ã¹ãã ããã³ã©ã€ããµã€ã¯ã«ããã¯ã«äŸåãããããæ£åžžã«æ©èœããã«ã¯ã¢ã¯ãã£ããªVueã³ã³ããã¹ããå¿
èŠã§ããæšæºçãªãã¯ããã¯ã¯ãsetupå
ã§ã³ã³ããŒã¶ãã«ãã€ã³ã¹ã¿ã³ã¹åããæå°éã®ã©ãããŒã³ã³ããŒãã³ããäœæããããšã§ããVitestãšVue Test Utilsãå¿
èŠãªããŒã«ãæäŸããŸãã
import { describe, it, expect } from 'vitest'
import { mount } from '@vue/test-utils'
import { defineComponent, h } from 'vue'
import { useCounter } from './useCounter'
function withSetup<T>(composable: () => T): { result: T; unmount: () => void } {
let result!: T
const wrapper = mount(
defineComponent({
setup() {
result = composable()
return () => h('div')
}
})
)
return { result, unmount: () => wrapper.unmount() }
}
describe('useCounter', () => {
it('initializes with default value', () => {
const { result } = withSetup(() => useCounter())
expect(result.count.value).toBe(0)
})
it('respects min and max boundaries', () => {
const { result } = withSetup(() =>
useCounter({ min: 0, max: 3, initialValue: 3 })
)
result.increment()
expect(result.count.value).toBe(3) // Capped at max
result.count.value = 0
result.decrement()
expect(result.count.value).toBe(0) // Capped at min
})
it('computes doubled value reactively', () => {
const { result } = withSetup(() => useCounter({ initialValue: 5 }))
expect(result.doubled.value).toBe(10)
result.increment()
expect(result.doubled.value).toBe(12)
})
})ãŠãŒãã£ãªãã£é¢æ°withSetupã¯ãããããã³ã³ããŒã¶ãã«ã®ãã¹ãã«åå©çšå¯èœãªãã¿ãŒã³ã§ããå¯äžã®åœ¹å²ããªã¢ã¯ãã£ãã³ã³ããã¹ãå
ã§ã³ã³ããŒã¶ãã«ãå®è¡ããããšã§ããæå°éã®ã³ã³ããŒãã³ããããŠã³ãããŸããunmountã¡ãœããã®è¿åŽã«ãããonUnmountedããã¯ã®ã¯ãªãŒã³ã¢ããåäœã®ãã¹ããå¯èœã«ãªããŸãã
ãã¹ãã¯3ã€ã®æ¬è³ªçãªåŽé¢ãã«ããŒããŠããŸããããã©ã«ãå€ã§ã®åæåãããžãã¹å¶çŽïŒmin/maxã®å¢çïŒã®éµå®ããããŠç®åºå€ã®ãªã¢ã¯ãã£ããã£ã§ãããã®ç¶²çŸ ç¯å²ã¯ãæ¬çªã³ã³ããŒã¶ãã«ã«æåŸ ãããæäœéã®ãã¹ãã«ãã¬ããžã§ãã
useFetchDataã®ãããªéåæã³ã³ããŒã¶ãã«ã®å Žåãfetchã®ã¢ãã¯ãšflushPromises()ã䜿çšããŠã¢ãµãŒã·ã§ã³åã«Promiseã®è§£æ±ºãåŸ
ã€å¿
èŠããããŸãããã®ãããã¯ã¯ãVueã§ã®éåæãã¹ãã®ç¿ç床ãè©äŸ¡ããå
žåçãªé¢æ¥ã®è³ªåã§ãã
VueUseã¯ããã©ãŠã¶ã€ã³ã¿ã©ã¯ã·ã§ã³ãã»ã³ãµãŒãã¢ãã¡ãŒã·ã§ã³ããªã¢ã¯ãã£ããŠãŒãã£ãªãã£ãã«ããŒãã200以äžã®ã³ã³ããŒã¶ãã«ãæäŸããŠããŸããã«ã¹ã¿ã ã³ã³ããŒã¶ãã«ãéçºããåã«ãVueUseãæ¢ã«è§£æ±ºçãæäŸããŠããã確èªããããšãæšå¥šãããŸãããã ããæ¬çªç°å¢ã§ãããã®ã³ã³ããŒã¶ãã«ãé©å¿ãæ¡åŒµããããã°ããããã«ã¯ãåºç€ãšãªããã¿ãŒã³ã®çè§£ãäžå¯æ¬ ã§ãã
æè¡é¢æ¥ã§é »åºãã質å
Vue 3ã®ã³ã³ããŒã¶ãã«ã¯ã2026å¹Žã®æè¡é¢æ¥ã«ãããŠé¿ããŠéããªããããã¯ã§ãã以äžã®è³ªåã¯ãäžçŽããã·ãã¢ã¬ãã«ãŸã§ãæ¡çšæ åœè ãè©äŸ¡ãããã€ã³ããç¶²çŸ ããŠããŸãã
ã³ã³ããŒã¶ãã«ãšããã¯ã¹ã€ã³ã®éãã¯äœã§ããïŒ Vue 2ã®ããã¯ã¹ã€ã³ã¯ãªãã·ã§ã³ãã³ã³ããŒãã³ãã«ããŒãžãããããååã®è¡çªãããŒã¿ãœãŒã¹ã®äžéææ§ãåä»ãã®æ¬ åŠãåŒãèµ·ãããŸããã³ã³ããŒã¶ãã«ã¯åä»ãã®æ»ãå€ãéããŠæç€ºçãªã€ã³ã¿ãŒãã§ãŒã¹ãå ¬éãããã¹ãã©ã¯ãã£ãªã³ã°ã«ãããªããŒã ãå¯èœã«ããäŸåé¢ä¿ãéæã«ããŸããä¿å®æ§ããã¹ãå®¹ææ§ãTypeScriptäºææ§ã®ãã¹ãŠã«ãããŠã³ã³ããŒã¶ãã«ãåªããŠããŸãã
ã³ã³ããŒã¶ãã«å
ã®å¯äœçšã¯ã©ã®ããã«ç®¡çãã¹ãã§ããïŒ
ãã¹ãŠã®å¯äœçšïŒã€ãã³ããªã¹ããŒãã¿ã€ããŒãWebSocketãµãã¹ã¯ãªãã·ã§ã³ïŒã¯ãonUnmountedããã¯ãŸãã¯watchEffectã®ã¯ãªãŒã³ã¢ããæ©æ§ã§è§£é€ããå¿
èŠããããŸãããã®ã¯ãªãŒã³ã¢ãããæ ããšãã¡ã¢ãªãªãŒã¯ãã³ã³ããŒãã³ãã®åããŠã³ãæã®äºæããªãåäœãåŒãèµ·ãããŸãã
setup()ã®å€ã§ã³ã³ããŒã¶ãã«ãåŒã³åºãããšã¯å¯èœã§ããïŒ
ã§ããŸãããã³ã³ããŒã¶ãã«ã¯ã³ã³ããŒãã³ãã®setup()颿°å
ããŸãã¯ä»ã®ã³ã³ããŒã¶ãã«å
ã§åæçã«åŒã³åºãå¿
èŠããããŸãããã®ã³ã³ããã¹ãå€ã§ã®åŒã³åºãã¯ãVueãã©ã€ããµã€ã¯ã«ããã¯ãšãªã¢ã¯ãã£ããã£ãã³ã³ããŒãã³ãã€ã³ã¹ã¿ã³ã¹ã«é¢é£ä»ããããšã劚ããŸãã
ã³ã³ããŒã¶ãã«ã§ã°ããŒãã«ç¶æ
ãå
±æããã«ã¯ã©ãããã°ããã§ããïŒ
äž»ã«2ã€ã®ã¢ãããŒãããããŸããã·ã³ã°ã«ãã³ãã¿ãŒã³ã¯ãã³ã³ããŒã¶ãã«é¢æ°ã®å€ïŒã¢ãžã¥ãŒã«ã¬ãã«ïŒã§ãªã¢ã¯ãã£ããªã€ã³ã¹ã¿ã³ã¹ãäœæãããã¹ãŠã®ã³ã³ã·ã¥ãŒããŒéã§å
±æããŸããprovide/injectãã¿ãŒã³ã¯ãã³ã³ããŒãã³ãããªãŒãå©çšããŠç¶æ
ãã³ã³ããã¹ãçã«äŒæããŸããçã«ã°ããŒãã«ãªç¶æ
ã«ã¯ã·ã³ã°ã«ãã³ããµãããªãŒã«ã¹ã³ãŒããããç¶æ
ã«ã¯provide/injectãé©ããŠããŸãã
ã©ã€ããµã€ã¯ã«ããã¯ã䜿çšããã³ã³ããŒã¶ãã«ãã©ããã¹ãããŸããïŒ
åè¿°ã®withSetupãã¯ããã¯ã¯ãå¿
èŠãªVueã³ã³ããã¹ããæäŸããæå°éã®ã©ãããŒã³ã³ããŒãã³ããäœæããŸããonMountedã®ãããªããã¯ã®å ŽåãVue Test Utilsã䜿çšããŠå®DOMïŒãŸãã¯ä»®æ³DOMïŒã«ã³ã³ããŒãã³ããããŠã³ãããå¿
èŠããããŸããã©ã€ããµã€ã¯ã«ããã¯ã䜿çšããªãçŽç²ã«ãªã¢ã¯ãã£ããªã³ã³ããŒã¶ãã«ã¯ãVueã®effectScopeã§ããç°¡æœã«ãã¹ãã§ããŸãã
SSRç°å¢ã§ã®éåæã³ã³ããŒã¶ãã«ã«ã¯ã©ã®ãããªæŠç¥ãå¿
èŠã§ããïŒ
SSRã³ã³ããã¹ãïŒNuxtãŸãã¯VueãµãŒããŒãµã€ãã¬ã³ããªã³ã°ïŒã§ã¯ãéåæã³ã³ããŒã¶ãã«ã«ç¹å¥ãªé
æ
®ãå¿
èŠã§ããonMountedã¯ãµãŒããŒãµã€ãã§ã¯å®è¡ããããfetchåŒã³åºãã¯ãã¬ãŒã ã¯ãŒã¯ã®SSRæ©æ§ïŒNuxtã®useAsyncDataãªã©ïŒãéããŠç®¡çããå¿
èŠããããŸããã³ã³ããŒã¶ãã«ã¯å®è¡ç°å¢ãå€å¥ããããã«å¿ããŠåäœãé©å¿ãããå¿
èŠããããŸãã
ä»ããç·Žç¿ãå§ããŸãããïŒ
颿¥ã·ãã¥ã¬ãŒã¿ãŒãšæè¡ãã¹ãã§ç¥èããã¹ãããŸãããã
ãŸãšã
Vue 3ã®ã³ã³ããŒã¶ãã«ã¯ãããã¯ã¹ã€ã³ããã®åãªãæ§æçãªé²åã§ã¯ãããŸãããããã³ããšã³ãã¢ããªã±ãŒã·ã§ã³ã«ãããåå©çšå¯èœãªããžãã¯ã®æ§é åæ¹æ³ã«é¢ãããã©ãã€ã ã·ãããäœçŸããŠããŸãããã®ç¿åŸã¯ãæ¬çªéçºãšæè¡é¢æ¥ã®äž¡æ¹ã«ãããŠå€§ããªç«¶äºåªäœæ§ãšãªããŸãã
éèŠãªãã€ã³ãã¯ä»¥äžã®éãã§ãã
- å³å¯ãªåå®çŸ©ïŒãªãã·ã§ã³ãšæ»ãå€ã«æç€ºçãªã€ã³ã¿ãŒãã§ãŒã¹ãå®çŸ©ããããšã§ãä¿¡é Œæ§ãšéçºè äœéšãåäžããŸã
- ã©ã€ããµã€ã¯ã«ç®¡çïŒã¡ã¢ãªãªãŒã¯ãé²ããããå¯äœçšã¯åžžã«
onUnmountedã§ã¯ãªãŒã³ã¢ããããå¿ èŠããããŸã - åæïŒã¢ããªã·ãã¯ãªã³ã³ããŒã¶ãã«ã§ã¯ãªããçµã¿åããå¯èœãªç¹åããã³ã³ããŒã¶ãã«ãèšèšããããšãéèŠã§ã
- 粟å¯ãªãªã¢ã¯ãã£ããã£ïŒ
MaybeRefOrGetterãtoValueãwatchEffectãæŽ»çšããŠæè»æ§ãæå€§åããŸã - äŸåæ§æ³šå
¥ïŒã³ã³ããŒãã³ãããªãŒå
ã®ç¶æ
å
±æã«ã¯ãåä»ã
InjectionKeyãšprovide/injectã䜿çšããŸã - ãã¹ãå®¹ææ§ïŒ
withSetupãã¯ããã¯ã¯ãåã³ã³ããŒã¶ãã«ãç¬ç«ããŠæ€èšŒããããã®æå°éã®ãªã¢ã¯ãã£ãã³ã³ããã¹ããæäŸããŸã - ããªããŒã·ã§ã³ïŒããžãã¹ã«ãŒã«ãããªããŒã·ã§ã³çšã³ã³ããŒã¶ãã«ã«ã«ãã»ã«åããããšã§ãã¢ããªã±ãŒã·ã§ã³å šäœã®äžè²«æ§ãä¿èšŒãããŸã
å ç¢ãªã³ã³ããŒã¶ãã«ãèšèšãåæããã¹ãããèœåã¯ã2026幎ã®Vueåžå Žã«ãããŠã·ãã¢ã¬ãã«ã®äººæãèå¥ããææšã§ãããããã®ãã¿ãŒã³ã¯ãVue 3ããã³Nuxt 3ã®æ¬çªã¢ããªã±ãŒã·ã§ã³ãæ¯ããæè¡åºç€ãšãªããŸãã
ä»ããç·Žç¿ãå§ããŸãããïŒ
颿¥ã·ãã¥ã¬ãŒã¿ãŒãšæè¡ãã¹ãã§ç¥èããã¹ãããŸãããã
ã¿ã°
å ±æ
é¢é£èšäº

Vue 3 Pinia vs VuexåŸ¹åºæ¯èŒïŒ2026幎ã®ç¶æ 管çãšé¢æ¥å¯Ÿçã¬ã€ã
Vue 3ã«ãããPiniaãšVuexã®éãã培åºçã«æ¯èŒããŸããOptions StoreãšSetup StoreãTypeScriptçµ±åãã¹ãã¢ééä¿¡ãVuexããã®ç§»è¡æé ãSSR察å¿ãŸã§ã2026幎ã®é¢æ¥ã§åãããç¶æ 管çã®ç¥èãå®è·µçãªã³ãŒãäŸãšãšãã«è§£èª¬ããŸãã

Nuxt 4ã®æ°æ©èœã培åºè§£èª¬ïŒãã£ã¬ã¯ããªæ§é ã®å·æ°ãšNuxt 3ããã®ç§»è¡ã¬ã€ãïŒ2026幎çïŒ
Nuxt 4ã§å°å ¥ãããapp/ãã£ã¬ã¯ããªæ§é ãã·ã³ã°ã«ãã³ããŒã¿ãã§ããå±€ãshallow reactivityãTypeScriptåå²ã³ã³ããã¹ãã«ã€ããŠãã³ãŒãäŸã亀ããŠè©³ãã解説ããŸãã

Vue.js颿¥ã®å¿ é 質å: å å®ãã€ããããã®25å
Vue.js颿¥ã®æºåã«åœ¹ç«ã€å¿ é 質å25åããªã¢ã¯ãã£ããã£ããcomposablesãŸã§ã次ã®é¢æ¥ãåã¡æãããã®èŠç¹ãæŒãããŸãã