Nuxt Composition API 備忘録
Nuxt Composition API (TypeScript) の基本を雑にメモするスクラップ
プロジェクト作成 & インストール
> yarn create nuxt-app ${プロジェクト名}
> cd ${プロジェクト名}
> yarn add --dev @nuxtjs/composition-api
{
buildModules: [
'@nuxtjs/composition-api/module', // 追加
],
}
> npx nuxt -v
@nuxt/cli v2.15.6
defineComponent
defineComponent
を利用してコンポーネントを作成する
data
, methods
やライフサイクルを setup()
内に書く
setup()
で返した値を template
側で利用できる
サンプルプログラム
<template>
<div>
<h1>{{ text }}</h1>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const text = 'Hello, World!'
return {
text,
}
},
})
</script>
ref
プリミティブ(Object 以外)の値をリアクティブにするときは ref
を使う
値の参照(更新)には .value
でアクセスする
const sample = ref('Hello, World!') // 引数に初期値を指定する
console.log(sample.value) // -> Hello, World!
サンプルプログラム
<template>
<div>
<h1>{{ text }}</h1>
<button @click="changeText">button</button>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const text = ref('Hello, World!')
const changeText = () => {
text.value = 'Hello, Nuxt!'
}
return {
text,
changeText,
}
},
})
</script>
reactive
Object の値をリアクティブにするときは reactive
を使う
const sample = reactive({
str: 'abc',
num: 100,
})
console.log(sample.str) // -> 'abc'
reactive
から一部のプロパティだけ取り出す場合は toRef()
か toRefs()
を使う
toRefs()
の方が楽に書ける
const str = toRef(sample, 'str') // toRef(reactive値, '取り出すキー')
// or
const { str } = toRefs(sample) // toRefs(reactive値)
サンプルプログラム
<template>
<div>
<h1>{{ state.text }}</h1>
<p>{{ state.count }}</p>
<button @click="changeText">button</button>
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, toRefs } from '@nuxtjs/composition-api'
interface State {
text: string
count: number
}
export default defineComponent({
setup() {
const state = reactive<State>({
text: 'Hello, World!',
count: 0,
})
const count = toRef(state, 'count')
// or
const { count } = toRefs(state)
const changeText = () => {
state.text = 'Hello, Nuxt!'
count.value++
}
return {
state,
changeText,
}
},
})
</script>
ライフサイクル
ライフサイクルは onMounted
のように onXXX
が用意されている
onMounted(実行する処理)
を setup()
内に書く
同じライフサイクルを複数回呼び出すこともできる
サンプルプログラム
<template>
<div>
<ul v-for="(log, i) in state.logs" :key="i">
<li>{{ log }}</li>
</ul>
</div>
</template>
<script lang="ts">
import {
defineComponent,
onBeforeMount,
onMounted,
reactive,
} from '@nuxtjs/composition-api'
interface State {
logs: string[]
}
export default defineComponent({
setup() {
const state = reactive<State>({
logs: [],
})
onBeforeMount(() => {
state.logs.push('onBeforeMount')
})
onMounted(() => {
state.logs.push('onMounted1')
})
onMounted(() => {
state.logs.push('onMounted2')
})
return {
state,
}
},
})
</script>
computed, watch
ライフサイクルと同じように setup()
内に
computed(実行する処理)
, watch(監視対象, 変更時の処理 [, オプション])
を書く
サンプルプログラム
<template>
<div>
<div v-for="(name, i) in names" :key="i">
<input v-model="checkedNames" :value="name" type="checkbox" />
<label>{{ name }}</label>
</div>
<p>{{ text }}</p>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, ref, watch } from '@nuxtjs/composition-api'
export default defineComponent({
setup() {
const names = ['Jack', 'John', 'Mike']
const checkedNames = ref<string[]>([])
// computed版
const text = computed(() => {
return 'checkedNames: ' + checkedNames.value.join(', ')
})
// watch版
const text = ref('')
watch(
checkedNames,
(newValue, oldValue) => {
text.value = 'checkedNames: ' + newValue.join(', ')
},
{ immediate: true, deep: false }
)
return {
names,
checkedNames,
text,
}
},
})
</script>
props, context
setup(props, context)
のように setup()
から受け取れる
props
の値を分割代入するときは toRefs()
を使う
context
からは attrs
, slots
, emit
にアクセスできる
setup(props, { attrs, slots, emit })
のように分割もできる
サンプルプログラム
<template>
<div>
<h2>Parent</h2>
<div v-for="(name, i) in names" :key="i">
<input v-model="selectedName" :value="name" type="radio" />
<label>{{ name }}</label>
</div>
<child :name="selectedName" @reset="onReset"></child>
</div>
</template>
<script lang="ts">
import { defineComponent, ref } from '@nuxtjs/composition-api'
import Child from '~/components/Child.vue'
export default defineComponent({
components: { Child },
setup() {
const names = ['Jack', 'John', 'Mike']
const selectedName = ref('')
const onReset = () => {
selectedName.value = ''
}
return {
names,
selectedName,
onReset,
}
},
})
</script>
<template>
<div>
<h2>Child</h2>
<p>{{ text }}</p>
<button @click="reset">Reset</button>
</div>
</template>
<script lang="ts">
import { computed, defineComponent, toRefs } from '@nuxtjs/composition-api'
export default defineComponent({
props: {
name: {
type: String,
default: '',
},
},
emits: ['reset'],
setup(props, { emit }) {
const { name } = toRefs(props)
const text = computed(() => {
return name.value.toUpperCase()
})
const reset = () => {
emit('reset')
}
return {
text,
reset,
}
},
})
</script>
composable
setup()
内のロジックを別ファイルに分離できる
サンプルプログラム
import { computed, ref } from '@nuxtjs/composition-api'
/** 反対色を返す関数 */
const getOpposite = (color: string) => {
return (
'#' +
color
.match(/^#(.{2})(.{2})(.{2})$/)!
.slice(1, 4)
.map((v) => 255 - parseInt(v, 16))
.map((v) => ('0' + v.toString(16)).slice(-2))
.join('')
)
}
export default function useTheme() {
const mainColor = ref('#0080ff')
const accentColor = computed(() => {
return getOpposite(mainColor.value)
})
return {
mainColor,
accentColor,
}
}
<template>
<div>
<input type="color" v-model="mainColor" />
<p v-bind:style="{ color: mainColor }">main: {{ mainColor }}</p>
<p v-bind:style="{ color: accentColor }">accent: {{ accentColor }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent } from '@nuxtjs/composition-api'
import useTheme from '~/composable/useTheme'
export default defineComponent({
setup() {
const { mainColor, accentColor } = useTheme()
return {
mainColor,
accentColor,
}
},
})
</script>
provide, inject
provide
, inject
を使うと親子関係のコンポーネント間で値(state など)を共有できる
子孫コンポーネントでも親の値を受け取れるので props バケツリレーを回避できる
サンプルプログラム
export default function useTheme() { ... }
+ export type themeType = ReturnType<typeof useTheme>
+ export const themeKey: InjectionKey<themeType> = Symbol('themeKey')
<template>
<div>
<input type="color" v-model="mainColor" />
<p v-bind:style="{ color: mainColor }">main: {{ mainColor }}</p>
<p v-bind:style="{ color: accentColor }">accent: {{ accentColor }}</p>
<child></child>
</div>
</template>
<script lang="ts">
import { defineComponent, provide } from '@nuxtjs/composition-api'
import useTheme, { themeKey } from '~/composable/useTheme'
import Child from '~/components/Child.vue'
export default defineComponent({
components: { Child },
setup() {
const theme = useTheme()
// provide
provide(themeKey, theme)
const { mainColor, accentColor } = theme
return {
mainColor,
accentColor,
}
},
})
</script>
<template>
<div>
<h2>Child</h2>
<p v-bind:style="{ color: mainColor }">main: {{ mainColor }}</p>
<p v-bind:style="{ color: accentColor }">accent: {{ accentColor }}</p>
</div>
</template>
<script lang="ts">
import { defineComponent, inject } from '@nuxtjs/composition-api'
import { themeKey } from '~/composable/useTheme'
export default defineComponent({
setup() {
// inject
const { mainColor, accentColor } = inject(themeKey)!
return {
mainColor,
accentColor,
}
},
})
</script>