Composition API RFC Migration - ChoDragon9/posts GitHub Wiki
npm install @vue/composition-api
import Vue from 'vue'
import VueCompositionApi from '@vue/composition-api'
Vue.use(VueCompositionApi)
@vue/composition-api
๊ฐ ๋ณ๊ฒฝ ๋์์ ๋ ์ด๋ฅผ ์ฌ์ฉํ๋ ๊ฐ๋ฐ์๋ API ๋ณ๊ฒฝ/์ญ์ ์ ๋ํ ๊ถํ์ด ์๋ค.
๊ทธ๋์ ์
๋ฐ์ดํธ๊ฐ ๋๋ ์๊ฐ ํ๋ก์ ํธ์ ์ง์ ์ ์ธ ์ํฅ์ ์ ํํ๊ฒ ๋๋ค. ์ด๋ฐ ๋ถ๋ถ์ ๋๋ ์ธ๋ถ ์์กด์ฑ
์ด๋ผ๊ณ ๋ถ๋ฅธ๋ค.
์ธ๋ถ ์์กด์ฑ์ ์ง์ ์ ์ผ๋ก ์กฐ์ํ ์ ์๋ ๊ถํ์ด ์์์ผ๋ก ์์กด์ฑ์ด ๊ฐํ๋ค. ์ด๋ฅผ ์ฝํ๊ฒ ๋ง๋๋ ๊ฐ์ฅ ์ฌ์ด ๋ฐฉ๋ฒ์ ์ค๊ฐ์ ๋ฌด์ธ๊ฐ๋ฅผ ๊ปด๋ฃ๋ ๊ฒ์ด๋ค.
์ธ๋ถ ์์กด์ฑ์ ์ต์ํ ํ๋ฉด์ Composition API๋ฅผ ์์ ํ๊ฒ ์ ์ฉํ๊ธฐ ์ํ ๋์์ผ๋ก Wrapper ํจํด์ ์ฌ์ฉํ ์ ์๋ค.
๋ค์๊ณผ ๊ฐ์ด @vue/composition-api
์์ ์ฌ์ฉํ ๊ธฐ๋ฅ๋ง ์ถ์ถํ๊ณ export
ํ๋ค.
export {
defineComponent,
onMounted,
onBeforeMount,
ref,
reactive,
toRefs,
computed,
watch
} from '@vue/composition-api'
<template>
</template>
<script lang="ts">
import { defineComponent, ref } from '~/vue-wrapper'
export default defineComponent({
props: {
id: String,
label: String,
disabled: Boolean,
checked: Boolean
},
setup(props, context) {
const isChecked = ref(props.checked)
const toggleCheckBox = (): void => {
isChecked.value = !isChecked.value
context.emit('on-change', isChecked.value)
}
return { isChecked, toggleCheckBox }
}
})
</script>
์ผ๋ฐ์ ์ผ๋ก Custom Component => Vue
์ด๋ฐ ํํ๋ก ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ฅผ ์ง์ ์ฌ์ฉํ๊ฒ ๋๋ค.
๋ง์ฝ์ 100๊ฐ ์ด์์ Custom Component
๊ฐ ์๋ค๋ฉด Vue
์
๊ทธ๋ ์ด๋ ์ ๋ชจ๋ Custom Component
์ ๋ํ ์๋ ๋ณ๊ฒฝ์ด ํ์ํ๋ค.
Wrapper ํจํด์ ์ ์ฉํ๋ฉด ์์ ์ธ๊ธํ ํํ๋ Custom Component => Vue Wrapper => Vue
์ด๋ ๊ฒ ๋ณ๊ฒฝ๋๋ค.
๊ทธ๋์ Vue
์ ๋ณ๊ฒฝ์ด ๋ฐ์ํ์ ๋ Custom Component
๊ฐ ์๋ Vue Wrapper
๋ง ์์ ํ๋ฉด ๋๋ค.
ํนํ Wrapper ํจํด๋ฅผ ์ ์ฉํ์ ๋, Vue
์์ API์ ์ด๋ฆ์ด ๋ณ๊ฒฝ ๋์์ ๋ IDE์ Refactor ๊ธฐ๋ฅ์ ํตํด Function ์ด๋ฆ์ ์๋์ผ๋ก ๋ณ๊ฒฝ ํ ์ ์๊ธฐ ๋๋ฌธ์ ๋งค์ฐ ํธ๋ฆฌํ๋ค.
Composition API๋ ๋ค์๊ณผ ๊ฐ์ด ์ฌ์ฉํ ์ ์๋ค.
<template>
<div>
<div>{{ count }} {{ plusOne }}</div>
<div>{{ obj.foo }}</div>
<div>{{ firstName }} {{ lastName }}</div>
<div ref="templateRef"></div>
<a @click.prevent="increment">INCREMENT</a>
<my-button></my-button>
</div>
</template>
<script>
import {
defineComponent,
ref,
reactive,
computed,
toRefs,
watch,
onMounted,
onBeforeMount,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted
} from '@vue/composition-api'
import MyButton from './my-button.vue'
export default defineComponent({
components: {
MyButton
},
props: {
name: String
},
setup(props, context) {
console.log('[LIFECYCLE] beforeCreate, created')
console.log(`name is: ${props.name}`)
const count = ref(0)
const plusOne = computed(() => count.value + 1)
const obj = reactive({ foo: 'bar ' })
const state = reactive({
firstName: 'Peter',
lastName: 'Cho'
})
const templateRef = ref(null)
const increment = () => {
count.value++
context.emit('on-change', count.value)
}
watch(() => {
console.log(`count is ${count.value}`)
})
onBeforeMount(() => {
console.log('[LIFECYCLE] beforeMount')
})
onMounted(() => {
console.log('[LIFECYCLE] mounted')
})
onBeforeUpdate(() => {
console.log('[LIFECYCLE] beforeUpdate')
})
onUpdated(() => {
console.log('[LIFECYCLE] updated')
})
onBeforeUnmount(() => {
console.log('[LIFECYCLE] beforeDestroy')
})
onUnmounted(() => {
console.log('[LIFECYCLE] destroyed')
})
return {
count,
plusOne,
obj,
...toRefs(state),
templateRef,
increment
}
}
})
</script>
Vue Options API์์ ํ๋ฌ๊ทธ์ธ ์ฌ์ฉ ์, this.$router
ํํ๋ก ์ฌ์ฉํ๋ค. Composition API์์๋ setup(props, context)
์ ๋๋ฒ์งธ ์ธ์๋ก ์ ๋ฌ๋๋ context
๋ฅผ ์ฌ์ฉํ๋ค. context.root.$router
ํํ๋ก ํ๋ฌ๊ทธ์ธ์ ์ฌ์ฉํ ์ ์๋ค.
props๋ฅผ required๋ก ์ง์ ํ์ง ์์ผ๋ฉด Optional๋ก ์ฒ๋ฆฌ๋๋ค.
export default defineComponent({
props: {
id: String, // string | undefined
}
})
required๋ฅผ ์ฌ์ฉํ๋ฉด ํ์ ํ์ ์ผ๋ก ์ฒ๋ฆฌ๋๋ค.
export default defineComponent({
props: {
id: {
type: String,
required: true
}, // string
}
})
-
(์ปดํ์ผ ํ์) TypeScript๋ก props ํ์
์ ์ ์,
any
๋๋unknown
์ด ํ์ํ ๋๊ฐ ์๋ค. -
(๋ฐํ์) ์ด๋ props์ ํ์
์
null
๋ก ์์ฑํด์ผ ํ๋ค.
::: tip
Composition API ๊ณต์ ๋ฌธ์์๋ undefined
์ null
๋ก ์ฒ๋ฆฌ ๊ฐ๋ฅํ๋ค๊ณ ๊ฐ์ด๋ํ๋๋ฐ,
TypeScript๋ก ์ ์๋ Declaration ํ์ผ์์๋ null
๋ง ํ์ฉํ๋ค.
:::
interface CommonSelectOption {
value: unknown;
}
export default defineComponent({
props: {
value: null,
},
// ... ์๋ต
})
ref
๋ฅผ ์ฌ์ฉํ ๊ฒฝ์ฐ ๊ฐ์ ์ ๊ทผํ ๋ .value
๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
๊ทธ๋ฐ๋ฐ ๋๋ ref๋ฅผ ์ฌ์ฉํ์ ๋ ๋ถํธํจ์ ๋๊ผ๋ค.
๊ทธ๋์ ํน๋ณํ ๊ฒฝ์ฐ๊ฐ ์๋ ์ด์ ref
๋์ ์ reactive
๋ก ์ํ๋ฅผ ์ ์ํ๊ณ , reactive
๋ก ์ ์ํ ๋ณ์๋ฅผ returnํ ๋๋toRefs
๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ๊ฒฝํ์ ์ผ๋ก ์ข์๋ค.
setup(props, context) {
const inputText = ref('')
const isFocused = ref(false)
const onChange = (): void => {
context.emit('on-change', inputText.value)
}
const onFocus = (): void => {
isFocused.value = true // value์ ํ ๋น
}
const onBlur = (): void => {
isFocused.value = false // value์ ํ ๋น
}
onBeforeMount(() => {
inputText.value = props.value // value์ ํ ๋น
})
return {
inputText,
isFocused,
onChange,
onFocus,
onBlur
}
}
setup(props, context) {
const state = reactive({
inputText: '',
isFocused: false
})
const onChange = (): void => {
context.emit('on-change', state.inputText)
}
const onFocus = (): void => {
state.isFocused = true // state์ property๋ฅผ ํตํด ๊ฐ์ ํ ๋นํ๋ค.
}
const onBlur = (): void => {
state.isFocused = false // state์ property์ ๊ฐ์ ํ ๋นํ๋ค.
}
onBeforeMount(() => {
state.inputText = props.value // state์ property์ ๊ฐ์ ํ ๋นํ๋ค.
})
return {
...toRefs(state), // toRefs๋ฅผ ์ฌ์ฉํ์ฌ ref๋ก ๋ณํํ๋ค.
onChange,
onFocus,
onBlur
}
}
๋๋ reactive
์ Generic์ ์ฌ์ฉํ๋ ๊ฒ์ด ๊น๋ํ๋ค๊ณ ๋๊ผ๋ค.
interface Pagination {
currentPage: number
totalPage: number
}
const state = reactive<Pagination>({
currentPage: 1,
totalPage: 0
})
<template>
๋ด์์ <div ref="box">
์ ๊ฐ์ด TemplateRef
๋ฅผ ์ฌ์ฉํ ๋๋ ํญ์ ref
๋ก ์ ์ํ state
๋ฅผ ์ฌ์ฉํด์ผ ํ๋ค.
reactive
๋ก ์ ์ํ state
๋ ์ ์์ ์ผ๋ก ์ฐธ์กฐ๋์ง ์๋๋ค.
Options API์ Class-based API์์๋ this
์ปจํ
์คํธ๊ฐ ์กด์ฌ ํ๋ฏ๋ก this.$store
๋ฅผ ์ฌ์ฉํ๋ค.
๊ทธ๋ฌ๋ Composition API๋ this
์ปจํ
์คํธ๊ฐ ์๊ธฐ ๋๋ฌธ์ setup(props, context)
์ ๋ ๋ฒ์งธ ์ธ์๋ก ์ ๋ฌ๋๋ context.root.$store
๋ฅผ ํตํ์ฌ Vuex๋ฅผ ์ฌ์ฉํ๋ค.
useXXX
ํํ๋ก ์ฌ์ฉํ๋ฉด ๋ค๋ฅธ ์ฝ๋๋ค๊ณผ ์ ์ด์ธ๋ฆฌ๋ฆฌ๋ผ ์๊ฐํ๋ค.
import {SetupContext} from '@vue/composition-api';
export const useStore = (context: SetupContext) => {
const {
root: {
$store: {
state,
getters,
commit,
dispatch,
},
},
} = context;
return {
state,
getters,
commit,
dispatch,
};
};
state
๋ reactive
์ ref
๋ฅผ ํตํด ๋ฐ์ํ ์ํ๋ฅผ ๋ง๋ค์ด๋ mutation
์ ๊ฐ์งํ์ง ๋ชปํ๊ธฐ ๋๋ฌธ์ state
๋ฅผ ์ง์ ์ฌ์ฉํด์ผ ํ๋ค.
export default defineComponent({
setup(props, context) {
const { dispatch, state } = useStore(context)
const fetchCheckLogin = (): Promise<void> => {
return dispatch('auth/fetchCheckLogin')
}
const fetchLogout = (): Promise<void> => {
return dispatch('auth/fetchLogout')
}
const isLogin = (): boolean => {
return state?.auth?.auth?.success
}
const toAuthTitle = (): string => {
return isLogin() ? '๋ก๊ทธ์์' : '๋ก๊ทธ์ธ'
}
const logout = (): void => {
fetchLogout()
}
onMounted(() => {
fetchCheckLogin()
})
return {
toAuthTitle,
logout
}
}
})
reactive
๋ก ์ ์ํ ๋ฐ์ํ ์ํ์ useStore
๋ฅผ ํตํด ์ฌ์ฉํ๋ ์คํ ์ด ์ํ์ ๋ณ์๋ช
์ state
๋ก ์ฌ์ฉํ๊ณ ์๋ค.
setup()
๋ด๋ถ์ ์ฌ์ฉํ ๊ฒฝ์ฐ ์ถฉ๋์ด ๋๊ธฐ ๋๋ฌธ์ ๋ณ์๋ช
์ ๋ณ๊ฒฝ์ด ํ์ํ๋ค.
store
๋ฅผ ๋ค์์คํ์ด์ค๋ก ๊ฐ์ง๋ ๊ฒ๋ ํ๋์ ๋์์ด๋ค.
setup(props, context) {
const store = useStore(context) // store.state๋ก ์ ๊ทผ
}
๋ชจ๋ ํํ์ธ state๋ ์ฌ์ฉ ์ ๊น์ ์ ๊ทผ์ด ํ์ํ๋ค. computed
๋ฅผ ์ฌ์ฉํ๋ฉด ๊ธฐ์กด ์ต์
API ์ฒ๋ผ ์ฌ์ฉ๊ฐ๋ฅํ๋ค.
setup(props, context) {
const store = useStore(context)
const state = reactive({
auth: computed(() => store.state.authModule.auth)
})
}
์ฌ์ฉ์ธก์์ ๋ชจ๋๋ช
๊ณผ ์ก์
๋ช
์ ์ด๋ฒคํธ ๊ธฐ๋ฐ์ด๊ธฐ ๋๋ฌธ์ ๋ฐํ์์์๋ง ์ ์ ์๋์ ํ์ธํ ์ ์๋ค.
useAction
๋ ์ปดํ์ผํ์์ ๋ชจ๋๋ช
๊ณผ ์ก์
๋ช
์ด ์ ์์ ์ผ๋ก ์ฌ์ฉ๋์์์ ํ์ธํ๊ธฐ ์ํด ๋ง๋ค์ด์ก๋ค.
ModuleActions
์์ key๋ ๋ชจ๋๋ช
, value๋ ์ก์
๋ช
์ผ๋ก ํ์
์ ์ ์ํ๋ค.
- ๋ชจ๋๋ช ๊ณผ ํ์ผ๋ช ์ด ๋์ผํ๋๋ก ํ๋์ฝ๋ฉํด์ผ ํ๋ค.
- ์ก์
๋ช
์
keyof typeof T
ํํ๋ก ํ์ ์ด ์ ์๋๊ธฐ ๋๋ฌธ์ ์ก์ ์ด ์ถ๊ฐ๋๋ฉด ์๋์ผ๋ก ๋ฐ์๋๋ค.
๋จผ์ ๋ชจ๋๋ช ์ ๋ํด ํ์ ์ฒดํฌํ ๋ค, ํด๋น ๋ชจ๋์ ์ก์ ๋ช ์ ํ์ ์ฒดํฌํ๋ค. ํ๋๋ผ๋ ์ ์๋ ๋ช ์ ์ฌ์ฉํ์ง ์์ผ๋ฉด ์ปดํ์ผ ํ์ ์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
import { Dispatch } from 'vuex'
import { actions } from '~/store/auth'
import notice from '~/store/notice'
interface ModuleActions {
auth: keyof typeof actions
notice: keyof typeof notice.actions
}
type ActionHandle<Keys extends string> = {
[key in Keys]: (payload?: any) => Promise<any>
}
export const useStoreAction = (dispatch: Dispatch) => {
function useAction<T extends keyof ModuleActions>(
moduleName: T,
actions: ModuleActions[T][]
): ActionHandle<ModuleActions[T]> {
return Object.assign(
{},
...actions.map((action) => {
return {
[action]: (payload) => dispatch(`${moduleName}/${action}`, payload)
}
})
)
}
return { useAction }
}
์ฌ์ฉ ์ธก์ mapActions์ ์ ์ฌํ ํํ๋ก ์ฌ์ฉํ๋ค. ์ฒซ ๋ฒ์งธ ์ธ์๋ ๋ชจ๋๋ช , ๋ ๋ฒ์งธ ์ธ์๋ ์ก์ ๋ช ์ ๋ฐฐ์ด๋ก ์ฌ์ฉํ๋ฉฐ return type์ ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ Destructuring ํ์ฌ ์ฌ์ฉํ ์ ์๋ค.
const { dispatch } = useStore(context)
const { useAction } = useStoreAction(dispatch)
const { fetchCheckLogin, fetchLogout } = useAction('auth', [
'fetchCheckLogin',
'fetchLogout'
])
useAction๊ณผ ๊ฐ์ ์ด์ ๋ก ๋ง๋ค์ด์ง Getter๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํ ํจ์์ด๋ค.
import { getters } from '~/store/auth'
interface ModuleGetters {
auth: keyof typeof getters
}
type Getters<Keys extends string> = {
[key in Keys]: () => any
}
export const useStoreGetter = (getters: object) => {
function useGetter<T extends keyof ModuleGetters>(
moduleName: T,
moduleGetters: ModuleGetters[T][]
): Getters<ModuleGetters[T]> {
return Object.assign(
{},
...moduleGetters.map((name) => {
return {
[name]: (): any => getters[`${moduleName}/${name}`]
}
})
)
}
return { useGetter }
}
getter๋ ์ธ์๋ฅผ ๋ฐ์ง ์๋ ํจ์๋ก ๋ง๋ค์ด์ง๋ค.
const { getters } = useStore(context)
const { useGetter } = useStoreGetter(getters)
const { isLogin } = useGetter('auth', ['isLogin'])
const toAuthTitle = (): string => {
return isLogin() ? '๋ก๊ทธ์์' : '๋ก๊ทธ์ธ'
}
Store ๋ชจ๋์ ์ ์๋ Action
๊ณผ Getter
์ ํ์
์ ์ถ๋ก ํ ์ ์๋ค.
์ ์๋ถ๋ฅผ ์ฐพ์์ฃผ๋ ๊ธฐ๋ฅ(Navigate to declaration)๊ณผ ์ด๋ฆ๋ณ๊ฒฝ ๊ธฐ๋ฅ(Rename ๋๋ Refactor)์ ์ฌ์ฉํ ์ ์๋ค. ๊ธฐ์กด์ Vuex๋ ์ด๋ฌํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ ๋ ์ ํ์ด ์์๋ค.
๋ ํจ์๋ setup()
์ Context์ ์์กดํ์ง ์๋๋ค.
useStoreAction
๋ Dispatch
์ ์์กดํ๊ณ , useStoreGetter
๋ object
ํ์
์ ์์กดํ๋ค.
์ฆ, Middleware์์ ์ฌ์ฌ์ฉ์ ๊ฐ๋ฅํ๊ฒ ํด์ค๋ค.
import { useStoreGetter } from '~/use/useStoreGetter'
import { useStoreAction } from '~/use/useStoreAction'
export default ({ store: { dispatch, getters } }) => {
const { useAction } = useStoreAction(dispatch)
const { useGetter } = useStoreGetter(getters)
const { fetchCheckLogin, fetchLogout } = useAction('auth', [
'fetchCheckLogin',
'fetchLogout'
])
const { isLogin } = useGetter('auth', ['isLogin'])
return Promise.all([fetchCheckLogin(), fetchLogout()]).then(() =>
console.log(isLogin())
)
}
ActionTree๋ฅผ ์ฌ์ฉํ์ง ์๊ณ Plugin์ ์ฌ์ฉํ ์ผ์ด์ค
Store์ ActionTree ํ์
์ ์ฌ์ฉํ๋ฉด Action๋ช
([key: string])
์ ์ค์ง string
์ผ๋ก ์ถ๋ก ํ๋ค.
์ด์ ๋ํ ์ํฅ์ผ๋ก useAction์ ์๋์ ๋ค๋ฅด๊ฒ ๋์ํ๋ค.
export interface ActionTree<S, R> {
[key: string]: Action<S, R>;
}
๋ง์ฝ์ ActionTree๋ฅผ ์ฌ์ฉํ์ง ์์ ๊ฒฝ์ฐ Store๋ด์์ this
๋ฅผ ํตํด ํ๋ฌ๊ทธ์ธ์ ์ ๊ทผํ ์ ์๋ค.
๊ทธ๋์ ๊ณ ์ํด๋ธ ๋ฐฉ๋ฒ์ด useStorePlugin์ด๋ฉฐ ์ด๋ฅผ ํตํด ํ๋ฌ๊ทธ์ธ์ ์ ๊ทผํ ์ ์๋ค.
import { Store } from 'vuex'
export const useStorePlugin = (store: any) => {
const { $axios } = store as Store<any>
return { $axios }
}
export const actions = {
fetchCheckLogin(context): Promise<void> {
const { $axios } = useStorePlugin(this)
return $axios.$get('/check_login')
},
}
์์ ์๊ฐํ ์ฝ๋๋ฅผ ํตํ์ฌ ๊ฐ๋ฐ์ ์งํ ํ์ ๋ ์ฝ๋๋ฒ ์ด์ค๊ฐ ๋ํญ๊น์ง ์๋๊ณ ์ํญ ์ฆ๊ฐํ๋ค.
๊ทธ๋์ ๊ธฐ์กด์ ์ฌ์ฉ ์ค์ด๋ ์ฝ๋์ ๋ํด ๋์์ ๋์ผํ๋ ์ฝ๋๋ฒ ์ด์ค๋ฅผ ๊ฐ์ํ์ํค๋ ๋ฐฉ์์ด ํ์ํ๋ค.
์ฐ์ useStoreAction ์ฌ์ฉ๋ถ๋ฅผ ๊ฐ์ ํ ํ์๊ฐ ์๋ค๊ณ ๋๊ผ๋ค.
useStoreAction ์ฌ์ฉ๋ถ๋ฅผ ๋ณด๋ฉด ๋ฌด์ธ๊ฐ ๋ง์ด ์์ฑํด์ค์ผ ํ๋ ๊ฒ ๋๋ฌด ๋ง๋ค.
ํ์ฌ useStoreAction ์ฌ์ฉ๋ถ์๋ 1) ์ฌ์ฉํ ๋ชจ๋
, 2) ์ฌ์ฉํ ํจ์
, 3) ์ฌ์ฉํ ํจ์ ํด์ฒด์ ์ฝ๋
๋ฑ์ด ๊ธฐ์ ๋๊ณ ์๋ค.
useStoreAction์ ๋ชฉ์ ์ ํ์
์ถ๋ก ์ ํตํ ์ ์ธ๋ถ ์ถ์
์ ๋ํ ๋น์ค์ด ํฌ๊ธฐ ๋๋ฌธ์ ํ์
์ถ๋ก ๋ถ๋ถ ์ด์ธ์ ๋ํ ๊ฐ์ ์ด ํ์ํ๋ค.
๊ฐ์ ํ ๋ฐฉํฅ์ ๋จ์ํ 1) ์ฌ์ฉํ ๋ชจ๋
๋ง ๊ธฐ์ ํ๋ ๊ฒ์ด๋ค.
- const { fetchCheckLogin, fetchLogout } = useAction('auth', [
- 'fetchCheckLogin',
- 'fetchLogout'
- ])
+ const authActions = useAction('auth')
useStoreAction๋ ๋ชจ๋๋ช ์ ์ธ์๋ก ๋ฐ๊ณ ์ก์ ์ ๋ชจ๋ ๋ฐํํ๋ค. ๋ณ๊ฒฝ๋ ๋ถ๋ถ์ ๋ค์๊ณผ ๊ฐ๋ค.
const actionMap = new Map([
['auth', Object.keys(actions)],
['notice', Object.keys(notice.actions)]
]);
export const useStoreAction = (dispatch: Dispatch) => {
function useAction<T extends keyof ModuleActions>(
moduleName: T,
actions?: ModuleActions[T][]
): ActionHandle<ModuleActions[T]> {
const keys = actions || actionMap.get(moduleName) || [];
return Object.assign(
{},
...keys.map((action) => {
return {
[action]: (payload) => dispatch(`${moduleName}/${action}`, payload)
}
})
)
}
return { useAction }
};
v2.12.0๋ถํฐ ์๋ก์ด
fetch
์ธํฐํ์ด์ค๊ฐ ์ ์ฉ๋๋ค.fetch(context){}
ํํ์๋ค๋ฉดfetch(){}
ํํ๋ก ๋ฐ๋๋ค.middleware
๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํ๋ฉฐ,this
๋ฅผ ์ฌ์ฉํ๋๋ก ๋ฐ๋๋ค.
-
middleware
๋ก ์ฌ์ฉํ ๊ฒ์ ๊ถ์ฅํจ
-
defineComponent
ํจ์์fetch
,layout
,middleware
๋ฏธ์ง์ - ํ์ ํ์ฅ์ผ๋ก ํด๊ฒฐํ ๊ฒ์ ๊ถ์ฅํจ
-
fetch
๋ ํญ์Promise<void>
ํ์ ์ผ๋ก ๋ฐํํด์ผ ํจ
export default defineComponent({
fetch(context): Promise<void> {
}
})
import Vue from 'vue'
import { Context, Middleware } from '@nuxt/types'
declare module '*.vue' {
export default Vue
}
declare module 'vue/types/options' {
interface ComponentOptions<V extends Vue> {
fetch?(ctx: Context): Promise<void> | void
layout?: string | ((ctx: Context) => string)
middleware?: Middleware | Middleware[]
}
}
#261, #614 ๋ฒ์ ์ ๊ทธ๋ ์ด๋ ํ ๋ฐ์๋ ๊ฒ์ผ๋ก ๋ณด์
reactive์ ํ๋๋ก ์ง์ ๋ ํ์
์ด ๊ฐ์ฒด์ผ ๋, ํ๋์ ๋ด๋ถ์ ํ๋๊ฐ ์กด์ฌํ๊ฒ ๋๋ค.
์์ฑ ์๋๋ ํด๋น ํ๋์ ํ์
์ด์ง๋ง ํ์ฌ ํ์
์ถ๋ก ์ผ๋ก UnwrapRef
์ด ๋๋ค.
enum Axis {
One = 1,
Two = 2
}
interface MyInterfaceInInterface {
x: Axis // number or 1 | 2๋ ๋์ํจ
y: Axis
}
interface MyInterface {
field: MyInterfaceInInterface
}
export default defineComponent({
setup() {
const myState = reactive<MyInterface>({
field: {
x: 1,
y: 2
}
})
const add = (x: number, y: number) => x + y
add(myState.field.x, myState.field.y)
// Type Error
}
})
Argument type UnwrapRef3 is not assignable to parameter type number
Type UnwrapRef3 is not assignable to type number
reactive
์ ์ ์ธํ๋ ํ์
์ ์๋ํ ๋๋ก ๋์ํ์ง ์๋ ์ผ์ด์ค๊ฐ ์กด์ฌํ๋ค.
์๋ฅผ ๋ค๋ฉด ์
๋ ํธ ๋ฐ์ค์์ ์์ดํ
์ ์ ํํ๊ณ , BackEnd API๋ก ์์ฒญํ๋ ์ฌ๋ก์ด๋ค.
interface Options {
id: number
value: string
}
interface State {
options: Options[]
selected: Options | null
}
interface RequestBody {
selectedId: number
}
const state = reactive<State>({
options: [],
selected: null
})
const changeSelectedOption = (option) => {
state.selected = option
}
const onSave = () => {
if (state.selected === null) {
return
}
const body: RequestBody = {
selectedId: state.selected.id
}
}
๋ณด๊ธฐ์๋ ์ ์์ ์ธ ์ฝ๋๋ก ๋ณด์ด์ง๋ง state.selected.id
์์ TS2339: Property 'id' does not exist on type 'string'.
์๋ฌ๊ฐ ๋ฐ์ํ๋ค.
์๋ํ ๋๋ก ์คํ๋๊ฒ ํ๊ธฐ ์ํด์ Typecasting(as Type)
์ ํด์ผ ํ๋ค.
// Not Cool
const state = reactive<State>({
options: [],
selected: null
})
// Cool
const state = reactive({
options: [],
selected: null
}) as State
- Input: ์ปดํฌ๋ํธ ์ฌ์ฉ์ธก์์ ๋์ ์ปดํฌ๋ํธ์ ์ฃผ์ ํ ๊ฐ
- Output: ๋์ ์ปดํฌ๋ํธ์์ ์ปดํฌ๋ํธ ์ฌ์ฉ์ธก์ ์ ๋ฌํ ๊ฐ
Vue ์ปดํฌ๋ํธ๋ Input ์ญํ ์ ํ๋ Props๋ ์ค์ ๋ ๋ฒจ๋ก ์ ์ํ๋ค. ํ์ง๋ง Emit๋ ์ค์ ๋ ๋ฒจ๋ก ์ ์๋์ง ์๊ณ , ํ์ํ ๋ ํจ์๋ฅผ ํธ์ถํ๋ ํํ๋ก ๊ตฌ์ฑ๋๋ค. ์ปดํฌ๋ํธ์ ์คํฉ์ ์ฝ๊ฒ ํ์ ํ๊ธฐ ์ํด์๋ ํด๋น ๊ธฐ๋ฅ์ด ํ์ํ๋ค๊ณ ์๊ฐํ๋ค.
Composition API์ ๊ฒฝ์ฐ ์ต์๋จ์ ์ถ๊ฐ๋๋ฉด ์ฝ๊ฒ ํ์ ๊ฐ๋ฅํ ๊ฑฐ๋ผ ์๊ฐํ๋ค.
setup (props: Props, context) {
const emit = {
change: state => context.emit('change', {...state.surveyForm}),
changeImage: file => context.emit('change-image', file),
};
์ปดํฌ๋ํธ์ ์ ์๋๋ ํจ์์ ์ญํ ์ ์ํ๋ณ๊ฒฝ, ์ด๋ฒคํธ ๋ฆฌ์ค๋, ํฌํผ ํจ์๊ฐ ์๋ค. ๊ฐ ํจ์๋ ๊ฐ์ ๋ ๋ฒจ๋ก ์ ์ํ๊ธฐ ๋๋ฌธ์ ์๊ฐ์ ๊ตฌ๋ถ์ด ์์๋๋ค๊ณ ์๊ฐํ๋ค.
๋ํ setup
ํจ์ ๋ด๋ถ์ ํจ์ ์ถ๊ฐ ํ <template>
์ ์ฌ์ฉํ๋ ค๋ฉด ํ ๋น ๋ฐํ๊ฐ์ ์ถ๊ฐ๊ฐ ํ์ํ๋ค.
๊ฐ์ฒด์ ๋ด์ ์ ๋ฌํ๋ฉด ๋ฒ๊ฑฐ๋ก์๊ณผ ๋ฐํ๊ฐ์ ๊ฐ์ํ ๊ฐ๋ฅํ๋ค๊ณ ์๊ฐํ๋ค.
๊ฒฐ๋ก ์ ์ผ๋ก Vuex์ ๋ค์ด๋ฐ์ ๋ฐ๋ผ์ ์ํ๋ณ๊ฒฝ์ mutation
,
์ด๋ฒคํธ ๋ฆฌ์ค๋๋ action
์ผ๋ก ์ ์๋๋ฉด ์ด๋จ๊ฐ ์๊ฐํด๋ดค๋ค.
// AS IS
setup() {
const state = {}
const onChange = () => {}
const onClick = () => {}
return {state, onChange, onClick}
}
// TO BE
setup() {
const state = {}
const actions = {
onChange: () => {},
onClick: () => {}
}
return {state, actions}
}