zustand의 폴더구조는 아래와 같다.
1. handler.js
2. state.js
3. index.js
ts로 바뀔때는 그냥 전체 ts로 변경해주면된다.
1. handler.ts
2. state.ts
3. index.ts
import create from "zustand";
import { devtools } from "zustand/middleware";
const store = create(
devtools(
(set, get) => ({
...State
,...Handler(set,get)
})
)
);
export default store;
import create from "zustand";
import {devtools} from "zustand/middleware";
import state from "@/services/store/zustand/state";
import handler from "@/services/store/zustand/handler";
const store = create<{
state: stateType;
handler: handlerMethods;
}>(
devtools(
(set: any, get: any) => ({
state: {
...state, // 상태 초기화
},
handler: {
...handler(
set,
() => get().state, // getState 함수를 전달
() => get().handler // getHandler 함수를 전달
),
},
})
)
)
export default store;
const coverState = {
}
const createState = {
...coverState
}
export default createState;
export interface StateType {
t: boolean
}
const coverState: StateType = {
t: false
}
const createState = {
...coverState
}
export default createState;
const handler = (set, get) => ({
setName: (inputName) => {
set({Name: inputName});
},
});
export default handler;
typescript에서는
get: StateType | HandlerMethods
라고 해버리면,
getState()로 가져올때 어떤것이 state, handler인지 알수가없다.
그래서 state와 handler를 각각으로 나누어준다.
또한 index.ts에서
() => get().state, // getState 함수를 전달
() => get().handler // getHandler 함수를
라고 하는것은 아래의 state, handler를 각각 사용하고자 함이다.
type SetState<T> = (partial: Partial<T> | ((state: T) => void)) => void;
type Handler = (set: SetState<any>, getState: () => StateType, getHandler: () => Handler) => HandlerMethods;
export interface HandlerMethods {
setStore: (obj: any, callback?: any) => void;
}
const testHandler: Handler = (set,getState, getHandler) => ({
employee: async () => {
const {
state,
handler
} = doubleStore.getState();
const {
test1
} = state;
const {
setStore
} = handler;
//...
})
js -> ts로 변경하는것을 적용해보았다.
이후에 또 나타나는 이슈들에 대해서 추가할 예정이다.
interface StateType {
state: StateType;
handler: HandlerMethods;
}
const comparsionStore = create<StateType>(
devtools(
(set, get) => ({
state: {
...comparisonState, // 상태 초기화
},
handler: comparisonHandler(set, get),
}),
)
)
export interface HandlerMethods {
setHighlighteBtn: (obj: any, callback?: any) => void;
//...
}
const Handler = (set,get): HandlerMethods => ({
setHighlighteBtn: (flag) => {
set({state: {...get().state, highlighteBtn: flag}});
},
//...변경 없음
zustand state에서 depth가 1~5,6 까지 갈수잇으니
재귀함수를 사용하여서 하나씩 아래와 같은 형식을 하나씩 만드는것이 아닌,
// 적용전
setTempData: (list) => {
set((prev: any) => ({
state: {
...prev.state,
temp: {
...prev.state.temp,
...list,
},
},
}));
}
// 적용후
setTempData: (newData) => {
updateStatePropertyAtPath(set, ['temp'], newData)
},
재귀함수를 사용하여 번거로움을 덜어낼수있을것같다.
재귀함수는 아래의 경우를 생각하고있다.
// zustand state 변경을 위한 재귀 함수
const updateStatePropertyAtPath = <T extends object>(
set: (fn: (prevState: any) => any) => void,
path: Array<keyof T>,
newData: Partial<any>
) => {
set((prev: any) => {
const updateAtPath = (obj: any, path: Array<keyof T>, value: Partial<any>) => {
const [currentKey, ...restPath] = path;
if (!restPath.length) {
// 값이 객체인지 배열인지에 따라 다르게 처리
if (Array.isArray(value)) {
// 배열일 경우 새로 대체
return {
...obj,
[currentKey]: [...value], // 배열 교체
};
} else if (typeof value === 'object' && value !== null) {
// 객체일 경우 부분 병합
return {
...obj,
[currentKey]: {
...obj[currentKey],
...value, // 객체 병합
},
};
} else {
// 기본 자료형일 경우 그대로 대체
return {
...obj,
[currentKey]: value, // 기본 값 교체
};
}
}
// 아직 경로가 남아있다면 계속 재귀적으로 처리
return {
...obj,
[currentKey]: updateAtPath(obj[currentKey], restPath, value),
};
};
return {
state: updateAtPath(prev.state, path, newData),
};
});
};
export {updateStatePropertyAtPath}
위의 재귀함수를 기억해두면 다른곳에서도 충분히 사용할수잇을것 같다.