TL;DR

you, zustand react nextjs
Back
export const createUserSlice: StateCreator<UserSlice> = (set) =>
  stateCreator<UserSlice>('search', {
    name: '',
    status: false,
    gender: ''
  })(set)

Zustand를 쓸 때 다음과 같은 코드를 쓸 때가 많았다.

const createUserSlice: StateCreator<UserSlice> = (set) => ({
  user: {
    name: '',
    status: 'active',
    gender: 'Male',
    setName: (name: string) =>
      set(
        produce((draft) => {
          draft.user.name = name
        })
      ),
    setStatus: (status: UserStatus) =>
      set(
        produce((draft) => {
          draft.user.status = status
        })
      ),
    setGender: (gender: UserGender) =>
      set(
        produce((draft) => {
          draft.search.gender = gender
        })
      )
  }
})

액션 부분이 거의 중복되는 코드로 이루어져있었다. 그래서 set과 draft를 추상화했다.

const handleDraft =
  <S extends keyof Store>(slice: S) =>
  <T extends keyof Store[S]>(
    key: T,
    valueOrUpdater: ValueOrUpdater<Store[S][T]>
  ) =>
  (state: Store) =>
    produce(state, (draft) => {
      draft[slice][key] =
        typeof valueOrUpdater === 'function'
          ? (valueOrUpdater as Updater<Store[S][T]>)(draft[slice][key])
          : valueOrUpdater
    })

export const stateSetter =
  <S extends keyof Store>(
    slice: S,
    set: Get<Mutate<StoreApi<Store>, []>, 'setState', undefined>
  ) =>
  <T extends keyof Store[S]>(key: T) =>
  (valueOrUpdater: ValueOrUpdater<Store[S][T]>) =>
    set(handleDraft(slice)(key, valueOrUpdater))

export const createUserSlice: StateCreator<UserSlice> = (set) => {
  const userSetter = stateSetter('user', set)
  return {
    user: {
      name: '',
      status: 'active',
      gender: 'Male'
    },
    userActions: {
      setName: searchSetter('name'),
      setStatus: searchSetter('status'),
      setGender: searchSetter('gender')
    }
  }
}

훨씬 깔끔해졌지만 그래도 중복되는게 보인다. 이번엔 actions 객체를 만드는 함수를 만들었다.

export const actionGenerator = <T extends keyof Store>(
  key: T,
  states: Store[T],
  set: Get<Mutate<StoreApi<Store>, []>, 'setState', undefined>
): Setter<Store[T]> => {
  const setter = stateSetter(key, set)

  const actions: any = {}

  Object.keys(states).forEach((state) => {
    actions[`set${state[0].toUpperCase()}${state.slice(1)}`] = setter(
      state as keyof Store[T]
    )
  })

  return actions as Setter<Store[T]>
}

export const createUserSlice: StateCreator<UserSlice> = (set) => {
  const init = {
    name: '',
    status: 'active',
    gender: 'Male'
  }
  const userActions = actionGenerator('user', init, set)
  return {
    user: init,
    userActions: userActions
  }
}

이렇게 만들고 보니까 아예 slice를 만드는 함수를 만들고싶어졌다.

export const stateCreator =
  <S extends Slice>(key: keyof Store, init: Store[keyof Store]) =>
  (set: Get<Mutate<StoreApi<Store>, []>, 'setState', undefined>) => {
    const actions = actionGenerator(key, init, set)
    const slice = {
      [key]: init,
      [`${key}Actions`]: actions
    }

    return slice as unknown as S
  }

export const createUserSlice: StateCreator<UserSlice> = (set) =>
  stateCreator<UserSclie>('user', {
    name: '',
    status: 'active',
    gender: 'Male'
  })(set)

깔끔해졌다. 다만 중간에 구현한 유틸 함수들이 더 깔끔하게 만들어질수 있었을 것 같다. 내 typescript 경험치의 한계로 인해 as가 좀 들어간것도 좀 슬프다

© 5vermind.