
1. Store Reset
1-1) Pinia 사용
Vue3 프레임워크를 사용할 때 사용하는 중앙 상태 관리 라이브러리는 Pinia 다. Pinia 는 두 가지 방식으로 코드를 작성할 수 있다. 첫 번째는, Optional API 처럼(혹은 이전 중앙 상태 관리 라이브러리였던 Vuex 처럼) 사용하는 방법과 Composition API 처럼 사용하는 방법이다.
아래의 코드가 Optional API(혹은 Vuex) 처럼 사용하는 방법이다.
import { defineStore } from 'pinia'
export const useStore = defineStore('storeId', {
// arrow function recommended for full type inference
state: () => {
return {
// all these properties will have their type inferred automatically
count: 0,
name: 'Eduardo',
isAdmin: true,
items: [],
hasChanged: true,
}
},
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
// since we rely on `this`, we cannot use an arrow function
increment() {
this.count++
},
randomizeCounter() {
this.count = Math.round(100 * Math.random())
},
},
})
state 는 상태, getters 는 상태의 값을 추적해서 계산되는 값(useMemo 와 비슷하다), actions 는 상태를 조작하는 함수다. Vue 를 사용하다 보면, 객체 지향이라는 느낌이 다분히 들 때가 많은데 대표적으로 이런 코드 스타일을 볼 때다. 그리고, 이렇게 코드를 작성하는 것이 Vue 를 오래 사용한 사람이라면 익숙할 것이다.
반면, Composition API 처럼 작성하는 것은 다음과 같다. dlfm
import { defineStore } from 'pinia'
import {ref, computed } from 'vue'
export const useStore = defineStore('storeId', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
count.value++
}
const randomizeCounter = () => {
count.value = Math.round(100 * Math.random())
}
const name = ref("Eduardo")
const isAdmin = ref(true)
const items = ref([])
const hasChanged = ref(true)
return {
count,
doubleCount,
increment,
randomizeCounter,
name,
isAdmin,
items,
hasChanged
}
})
좀더, 연관 있는 것끼리 코드를 묶을 수 있고, 우리가 Composition API 로 코드를 작성하고 있는지라 코드 스타일이 비슷해서 이런 Setup Store 방식을 사용하고 있다.
1-2) $reset 을 지원하지 않는다..?
그런데 이렇게 Setup Store 를 사용하니 발생하는 문제가 있었다.
https://pinia.vuejs.kr/core-concepts/state#resetting-the-state
Vue 3의 공식 Store | Vue.js를 위한 직관적인 스토어
직관적이고 타입 안전한 가벼운 그리고 유연한 Vue 스토어
pinia.vuejs.kr
흠... 모든 상태를 reset 하는 $reset 메서드를 사용할 수 없다. 우리가 만드는 방식에 의하면 이 $reset 은 필수 불가결한 메서드인데... 이것을 사용할 수 없다니.. 이 말은 곧, 모든 상태마다 reset 하는 함수를 만들어서 $reset 함수 안에서 다 호출해야 한다는 것이다. 그당시 코드를 모방하면 아래와 같다. (전 포스팅에서도 언급했든 우리는 useState 를 만들어서 사용하고 있다)
// utils
export const useState = <T>(initState: T): [ShallowRef<T>, (newState: T) => void ] => {
const state = shallowRef<T>(initState)
const setState = (newState: T) => {
state.value = newState
}
return [state, setState]
}
// store
import { defineStore } from 'pinia'
import { useState } from '../utils'
export const useStore = defineStore('storeId', () => {
const [count, setCount] = useState(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
setCount(count.value + 1)
}
const randomizeCounter = () => {
setCount(Math.round(100 * Math.random()))
}
const resetCount = () => setCount(0)
const [name, setName] = useState("Eduardo")
const resetName = () => setName("Eduardo")
const [isAdmin, setIsAdmin] = useState(true)
const resetIsAdmin = () => setIsAdmin(true)
const [items, setItems] = useState([])
const resetItems = () => setItems([])
const $reset = () => {
resetCount()
resetName()
resetIsAdmin()
resetItems()
}
return {
...
}
})
이건 너무 비효율적이라 판단했다. 실제로 하나의 Store 를 이렇게 하드하게 reset 을 만들고 적용해봤는데 이렇게 개발하면 안 되겠다고 판단했다. 때문에 최대한 코드를 바꾸지 않고 수정할 방법을 모색해서 구현했다.
1-3) 최대한 효율적으로 $reset 만들기
이 당시, 나는 모던 자바스크립트 DeepDive 에서 함수 파트를 열심히 읽고 있는 중이었는데, 클로저에 굉장히 큰 감명을 받은 상태였다. 그래서 이 문제를 클로저로 해결해봤다.
// useResetTool.ts
import { useState as originUseState } from '../utils'
export const useResetTool = () => {
const resetFuncQueue: Array<() => void> = [];
/**
* 인자로 받은 reset 함수를 Queue 쌓는 함수
* @param resetFuncArgs : State 를 초기화 하는 reset 함수
*/
const useResetInject = (...resetFuncArgs: Array<() => void>) => {
resetFuncArgs.forEach((resetFunc) => resetFuncQueue.push(resetFunc));
};
/**
* useState 와 사용 방법이 리턴 값 제외 모두 동일하며 자동으로 resetFuncQueue 에 reset 함수를 등록
* @param initState : 상태 초기값이자, reset 할 때 상태에 들어갈 값
* @returns [state, setState, resetState]
*/
const useState = <T>(
initState: T
): [ShallowRef<T>, (newState: T) => void, () => void] => {
const [state, setState] = originUseState(initState);
const resetState = () => setState(initState);
useResetInject(resetState);
return [state, setState, resetState];
};
/**
* resetFuncQueue 에 들어가 있는 모든 reset 함수를 실행
*/
const $reset = () => {
resetFuncQueue.forEach((resetFunc) => resetFunc());
};
return {
useResetInject,
useState,
$reset
};
};
이렇게 만들면 useState 로 만든 상태들은 모두, reset 할 수 있는 함수들이 resetFuncQueue 안에 들어간다. useResetInject 는 resetFuncQueue 에 reset 함수를 넣는 기능을 한다. 혹여나 useState 를 사용하지 않는 상태가 있을 수 있어 만들어 둔 함수다. 이제 위의 예시로 든 Store 의 상태를 reset 해보자
import { defineStore } from 'pinia'
import { useResetTool } from '../utils'
export const useStore = defineStore('storeId', () => {
const { useState, $reset } = useResetTool()
const [count, setCount] = useState(0)
const doubleCount = computed(() => count.value * 2)
const increment = () => {
setCount(count.value + 1)
}
const randomizeCounter = () => {
setCount(Math.round(100 * Math.random()))
}
const [name, setName] = useState("Eduardo")
const [isAdmin, setIsAdmin] = useState(true)
const [items, setItems] = useState([])
return {
...
}
})
끝이다. 실제로 기존 코드에서 useResetTool 을 import 하고 호출한 것밖에 더 없다. 실제로 useResetTool 은 useState 말고도 더 많은 상태 관련 함수들이 들어간다(직접 커스터마이징한 useFetch, usePolling 등) 그렇기에, 모든 상태를 reset 할 수 있도록 도와주면서 실제로 지금까지 만든 코드에서 추가되는 코드가 별로 없어서 굉장히 유용하게 사용하고 있다.
'je개발 회고' 카테고리의 다른 글
[ je 개발 회고 ] H 프로젝트 회고 PPT (0) | 2024.03.12 |
---|---|
[ je 개발 회고 ] L 프로젝트 (7) - 이제 백엔드 개발에도 참여하겠습니다 (0) | 2024.03.11 |
[ je 개발 회고 ] L 프로젝트 (5) - Vue 로 방대한 양의 데이터 다룰 때 주의할 점 (1) | 2024.03.06 |
[ je 개발 회고 ] L 프로젝트 (4) - 페이지 루프 기능 도입 (0) | 2024.03.05 |
[ je 개발 회고 ] L 프로젝트 (3) - 요구 사항 폭탄 속에서 개발자로 살아남기 (1) | 2024.02.26 |