Composition API RFC Migration - ChoDragon9/posts GitHub Wiki
npm install @vue/composition-apiimport 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}
}