λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°

FrontEnd

zustand와 νƒ€μž…μŠ€ν¬λ¦½νŠΈ [κ³΅μ‹λ¬Έμ„œλ²ˆμ—­]

λ°˜μ‘ν˜•
 

GitHub - pmndrs/zustand: 🐻 Bear necessities for state management in React

🐻 Bear necessities for state management in React. Contribute to pmndrs/zustand development by creating an account on GitHub.

github.com

ν•΄λ‹Ή 라이브러리의 κ³΅μ‹λ¬Έμ„œμ—μ„œλ„ νƒ€μž…μŠ€ν¬λ¦½νŠΈ κ΄€μ μ—μ„œ κ½€λ‚˜ 배울 점이 λ§Žμ•„ 정리해둠.

zustand
 

κ³΅μ‹λ¬Έμ„œ 메인 핡심 발췌

링크 : https://github.com/pmndrs/zustand

μ™œ redux λŒ€μ‹   zustandλ₯Ό μ‚¬μš©ν•˜λ‚˜μš”?

μ™œ context λŒ€μ‹   zustandλ₯Ό μ‚¬μš©ν•˜λ‚˜μš”?

  • 적은 μƒμš©κ΅¬
  • 값이 λ°”λ€” λ•Œλ§Œ λ Œλ”λ§ν•¨. (κΈ°λ³Έ deepEqual)
  • μ€‘μ•™ν™”λœ μ•‘μ…˜ 베이슀 μƒνƒœ 관리

zustand redux처럼 μ“°κΈ°

개인적으둜 λ‚˜μ˜μ§€ μ•ŠμŒ

const types = { increase: 'INCREASE', decrease: 'DECREASE' }

const reducer = (state, { type, by = 1 }) => {
  switch (type) {
    case types.increase:
      return { grumpiness: state.grumpiness + by }
    case types.decrease:
      return { grumpiness: state.grumpiness - by }
  }
}

const useStore = create((set) => ({
  grumpiness: 0,
  dispatch: (args) => set((state) => reducer(state, args)),
}))

const dispatch = useStore((state) => state.dispatch)
dispatch({ type: types.increase, by: 2 })

// λ˜λŠ” redux-middlewareλ₯Ό μ‚¬μš©ν•˜μ‹­μ‹œμ˜€. 
// 메인 λ¦¬λ“€μ„œλ₯Ό μ—°κ²°ν•˜κ³  초기 μƒνƒœλ₯Ό μ„€μ •ν•˜λ©° 
// μƒνƒœ μžμ²΄μ™€ κΈ°λ³Έ API에 λ””μŠ€νŒ¨μΉ˜ κΈ°λŠ₯을 μΆ”κ°€ν•©λ‹ˆλ‹€. 
// https://codesandbox.io/s/amazing-kepler-swxol
import { redux } from 'zustand/middleware'

const useStore = create(redux(reducer, initialState))

zustand context처럼 μ“°κΈ°

이것도 λ‚˜μ˜μ§€ μ•ŠμŒ.
  • μ»¨ν…μŠ€νŠΈ λ³„λ‘œ μƒˆλ‘œ μŠ€ν† μ–΄λ₯Ό λ§Œλ“€μ–΄μ•Ό ν•˜λŠ” 경우 μœ μš©ν•¨
import create from 'zustand'
import createContext from 'zustand/context'

const { Provider, useStore } = createContext()

export default function App({ initialBears }) {
  return (
    <Provider
      createStore={() =>
        create((set) => ({
          bears: initialBears,
          increase: () => set((state) => ({ bears: state.bears + 1 })),
        }))
      }>
      <Button />
    </Provider>
  )
}

μ•„λž˜ μ˜ˆμ œλŠ” 별 μ˜λ―ΈλŠ” μ—†μ–΄λ³΄μž„

import create from "zustand";
import createContext from "zustand/context";

// Best practice: You can move the below createContext() and createStore to a separate file(store.js) and import the Provider, useStore here/wherever you need.

const { Provider, useStore } = createContext();

const createStore = () =>
  create((set) => ({
    bears: 0,
    increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
    removeAllBears: () => set({ bears: 0 })
  }));

const Button = () => {
  return (
      {/** store() - This will create a store for each time using the Button component instead of using one store for all components **/}
    <Provider createStore={createStore}>
      <ButtonChild />
    </Provider>
  );
};

const ButtonChild = () => {
  const state = useStore();
  return (
    <div>
      {state.bears}
      <button
        onClick={() => {
          state.increasePopulation();
        }}
      >
        +
      </button>
    </div>
  );
};

export default function App() {
  return (
    <div className="App">
      <Button />
      <Button />
    </div>
  );
}

νƒ€μž…μŠ€ν¬λ¦½νŠΈ κ°€μ΄λ“œ

TypeScriptλ₯Ό μ‚¬μš©ν•  λ•Œμ˜ 차이점은 create(...)λ₯Ό μž‘μ„±ν•˜λŠ” λŒ€μ‹  create<T>()(...)λ₯Ό μž‘μ„±ν•΄μ•Ό ν•©λ‹ˆλ‹€.
μ—¬κΈ°μ„œ TλŠ” μƒνƒœ νƒ€μž…μž…λ‹ˆλ‹€.
import create from 'zustand'

interface BearState {
  bears: number
  increase: (by: number) => void
}

const useStore = create<BearState>()((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
}))

초기 μƒνƒœμ—μ„œ νƒ€μž…μ„ μΆ”λ‘ ν•  순 μ—†λ‚˜μš”?

TLDR: μ œλ„€λ¦­ μƒνƒœνƒ€μž… TλŠ” invariant ν•©λ‹ˆλ‹€.

ν•¨μˆ˜μ˜ νŒŒλΌλ―Έν„° ν•¨μˆ˜μ˜ 리턴 νƒ€μž…κ³Ό ν•¨μˆ˜μ˜ λ¦¬ν„΄νƒ€μž…μ΄ 같은 경우 νƒ€μž…μŠ€ν¬λ¦½νŠΈλŠ” νƒ€μž…μ„ 잘λͺ» μΆ”λ‘ ν•©λ‹ˆλ‹€.

μ•„λž˜ create의 λ‹¨μˆœν™”ν•œ 버전을 λ΄…μ‹œλ‹€.

declare const create: <T>(f: (get: () => T) => T) => T

const x = create((get) => ({
  foo: 0,
  bar: () => get(),
}))
// `x` is inferred as `unknown` instead of
// interface X {
//   foo: number,
//   bar: () => X
// }

f의 νƒ€μž… (get: () => T) => Tμ—μ„œ
  • Tλ₯Ό λ¦¬ν„΄ν•˜λ©΄μ„œ Tλ₯Ό μ œκ³΅ν•˜μ§€λ§Œ,
  • TλŠ” get ν•¨μˆ˜μ˜ 리턴 νƒ€μž…μ„ ν†΅ν•΄μ„œλ„ μ˜΅λ‹ˆλ‹€.
  • TypeScriptλŠ” Tλ₯Ό unknown으둜 μΆ”λ‘ ν•˜κ³  ν¬κΈ°ν•©λ‹ˆλ‹€.
    • 리턴 νƒ€μž…μ˜ TλŠ” get()을 ν†΅ν•΄μ„œλ§Œ μ•Œ 수 μžˆλŠ”λ°,
    • get()의 νƒ€μž…μ€ T둜 μΆ”λ‘ λ©λ‹ˆλ‹€.
    • 즉 κ³ μ°¨ νƒ€μž… 좔둠을 ν—ˆμš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
μ•„λž˜ μ˜ˆμ‹œλ„ xκ°€ unknown이 λ©λ‹ˆλ‹€.
즉, xλŠ” string λŒ€μ‹  unknownμž…λ‹ˆλ‹€.
declare const createFoo: <T>(f: (t: T) => T) => T
const x = createFoo((_) => 'hello')
 
 
이제 λˆ„κ΅°κ°€λŠ” createFoo에 λŒ€ν•œ κ΅¬ν˜„μ„ μž‘μ„±ν•˜λŠ” 것이 λΆˆκ°€λŠ₯ν•˜λ‹€κ³  μ£Όμž₯ν•  수 있고, μ΄λŠ” μ‚¬μ‹€μž…λ‹ˆλ‹€.
ZusstandλŠ” νƒ€μž… λΆˆκ±΄μ „ν•¨μ„ ν—ˆμš©ν•¨μœΌλ‘œμ¨ 이 문제λ₯Ό μš°νšŒν•©λ‹ˆλ‹€.
import create from 'zustand/vanilla'
// get은 ({foo:number}) 리턴
const useStore = create<{ foo: number }>()((_, get) => ({
  foo: get().foo,
}))

이 μ½”λ“œλŠ” μ»΄νŒŒμΌλ˜μ§€λ§Œ, μ‹€ν–‰ν•˜λ©΄ μ–΄λ–»κ²Œ λ κΉŒμš”?
"Uncaught TypeError:Cannot read properties of undefined (reading 'foo')" μ˜ˆμ™Έλ₯Ό λ§Œλ‚˜κ²Œ λ©λ‹ˆλ‹€.
사싀 초기 μƒνƒœκ°€ μƒμ„±λ˜κΈ° 전에 get은 undefinedλ₯Ό λ°˜ν™˜ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.
(λ”°λΌμ„œ 초기 μƒνƒœλ₯Ό 생성할 λ•Œ get을 ν˜ΈμΆœν•˜μ§€ λ§ˆμ„Έμš”!)
get의 μ΅œμ’… νƒ€μž…μ€ () => { foo: number }이 λ κ² μ§€λ§Œ,
μ²˜μŒμ—λŠ” () => undefinedμž…λ‹ˆλ‹€.
 

컀링은 μ™œμ“°λ‚˜μš”?

TLDR: ν•΄λ‹Ή 이슈λ₯Ό μ°Έκ³ ν•˜μ„Έμš”. λͺ¨λ“  νƒ€μž…μ„ μž…λ ₯ν•˜μ§€ μ•ŠκΈ° μœ„ν•΄μ„œμž…λ‹ˆλ‹€ :  microsoft/TypeScript#10571.
μ•„λž˜μ˜ μ‹œλ‚˜λ¦¬μ˜€λ₯Ό λ΄…μ‹œλ‹€.

declare const withError: <T, E>(
  p: Promise<T>
) => Promise<[error: undefined, value: T] | [error: E, value: undefined]>
declare const doSomething: () => Promise<string>

const main = async () => {
  let [error, value] = await withError(doSomething())
}

 
μ—¬κΈ°μ„œ TλŠ” λ¬Έμžμ—΄λ‘œ μΆ”λ‘ λ˜κ³  EλŠ” unknown으둜 μΆ”λ‘ λ©λ‹ˆλ‹€.
이제 μ–΄λ–€ 이유둜 인해 doSomething()이 μ–΄λ–€ ν˜•νƒœμ˜ 였λ₯˜λ₯Ό λ˜μ§ˆμ§€ ν™•μ‹€ν•˜κΈ° λ•Œλ¬Έμ—
E에 Foo둜 νƒ€μž… 주석을 달고 μ‹ΆμŠ΅λ‹ˆλ‹€.
 
ν•˜μ§€λ§Œ μ§€κΈˆμ€ λͺ¨λ“  μ œλ„€λ¦­μ„ μ „λ‹¬ν•˜κ±°λ‚˜ μ „ν˜€ μ „λ‹¬ν•˜μ§€ μ•Šκ±°λ‚˜ 두 κ°€μ§€λ§Œ κ°€λŠ₯ν•©λ‹ˆλ‹€.
λ”°λΌμ„œ Eλ₯Ό Foo둜 주석 μ²˜λ¦¬ν•˜λŠ” 것과 ν•¨κ»˜ T에도 주석을 달μ•„μ•Ό ν•©λ‹ˆλ‹€.
 
이λ₯Ό νšŒν”Όν•˜κΈ° μœ„ν•΄ λŸ°νƒ€μž„μ— 아무 κ²ƒλ„ν•˜μ§€ μ•ŠλŠ” withError의 컀리 버전을 λ§Œλ“œλŠ” κ²ƒμž…λ‹ˆλ‹€.
λͺ©μ μ€ E에 주석을 λ‹¬λ„λ‘ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.
declare const withError: {
  <E>(): <T>(
    p: Promise<T>
  ) => Promise<[error: undefined, value: T] | [error: E, value: undefined]>
  <T, E>(p: Promise<T>): Promise<
    [error: undefined, value: T] | [error: E, value: undefined]
  >
}
declare const doSomething: () => Promise<string>
interface Foo {
  bar: string
}

// withError와 주석
const main = async () => {
  let [error, value] = await withError<Foo>()(doSomething())
}

 
이제 Tκ°€ μΆ”λ‘ λ˜κ³  E에도 주석을 달 수 μžˆμŠ΅λ‹ˆλ‹€.
ZustandλŠ” μƒνƒœ(첫 번째 νƒ€μž… λ§€κ°œλ³€μˆ˜)에 νƒ€μž… 주석을 λ‹¬κ³ μž ν•˜λŠ” 것과 λ™μΌν•œ μ‚¬μš© 사둀λ₯Ό 가지고 있으며,
λ‚˜λ¨Έμ§€ νƒ€μž… λ§€κ°œλ³€μˆ˜λŠ” μœ μΆ”λ˜λ„λ‘ ν—ˆμš©ν•©λ‹ˆλ‹€.
 
λ˜λŠ” νƒ€μž…μ„ μž…λ ₯ν•˜λŠ” λŒ€μ‹  μƒνƒœλ₯Ό μœ μΆ”ν•˜λŠ” combine을 μ‚¬μš©ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€...
import create from "zustand"
import { combine } from "zustand/middleware"

const useStore = create(combine({ bears: 0 }, (set) => ({
  increase: (by: number) => set((state) => ({ bears: state.bears + by })),
}))

combine μ‚¬μš© μ‹œ μ‘°μ‹¬ν•˜μ„Έμš”...

μš°λ¦¬λŠ” 당신이 λ§€κ°œλ³€μˆ˜λ‘œ λ°›λŠ” νƒ€μž…μœΌλ‘œ set, get, store의 νƒ€μž…μ„ μ•½κ°„ μ†μž„μœΌλ‘œμ¨ 좔둠을 λ‹¬μ„±ν•©λ‹ˆλ‹€.
첫번째 거짓말은 μƒνƒœκ°€ μ™„μ„±λœ νƒ€μž…μΈ κ²ƒμ²˜λŸΌ μž…λ ₯λœλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€.
μ™„μ„±λœ νƒ€μž…μ€ μ‹€μ œλ‘œ 첫 번째 λ§€κ°œλ³€μˆ˜μ™€ 두 번째 λ§€κ°œλ³€μˆ˜μ˜ 리턴 λͺ¨λ‘μ˜ 얕은 병합({ ...a, ...b })μž…λ‹ˆλ‹€.
 
두 번째 λ§€κ°œλ³€μˆ˜ get의 μΆ”λ‘ λœ type은 () => { bears: number }μ΄μ§€λ§Œ,
μ‹€μ œλ‘œλŠ” () => { bears: number, increase: (by: number) => void }이어야 ν•˜λ―€λ‘œ κ±°μ§“λ§μž…λ‹ˆλ‹€.
 
κ·Έλž˜λ„ useStoreλŠ” μ—¬μ „νžˆ μ˜¬λ°”λ₯Έ νƒ€μž…μ„ 가지고 μžˆμŠ΅λ‹ˆλ‹€.
예λ₯Ό λ“€μ–΄ useStore.getStateλŠ” () => { bears: number, increase: (by: number) => void }κ°€ λ©λ‹ˆλ‹€.
 
{ bears: number }λŠ” { bears: number, increase: (by: number) => void }의 μƒμœ„ νƒ€μž…μ΄κΈ° λ•Œλ¬Έμ— 거짓말이 μ•„λ‹™λ‹ˆλ‹€.
λ”°λΌμ„œ λŒ€λΆ€λΆ„μ˜ 경우 λ¬Έμ œκ°€ 없을 κ²ƒμž…λ‹ˆλ‹€.
 
ν•˜μ§€λ§Œ replaceλ₯Ό μ‚¬μš©ν•˜λŠ” λ™μ•ˆμ€ μ£Όμ˜ν•΄μ•Ό ν•©λ‹ˆλ‹€.
예λ₯Ό λ“€μ–΄ set({ bears: 0 }, true)λŠ” μ»΄νŒŒμΌλ˜μ§€λ§Œ increase ν•¨μˆ˜λ₯Ό μ‚­μ œν•˜λ―€λ‘œ κ±΄μ „ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
set ν•¨μˆ˜κ°€ μ•„λ‹ˆλΌ λ°–μ—μ„œ μ„€μ •ν•˜λ©΄, 즉 useStore.setState({ bears: 0 }, true) μ΄λ ‡κ²Œ ν•˜λ©΄,
μ €μž₯μ†Œκ°€ increaseκ°€ λˆ„λ½λ˜μ—ˆμŒμ„ μ•ŒκΈ° λ•Œλ¬Έμ— μ»΄νŒŒμΌλ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
 
Object.keysλ₯Ό μˆ˜ν–‰ν•˜λ©΄ Object.keys(get())λŠ” ["bears"]κ°€ μ•„λ‹ˆλΌ ["bears", "increase"]λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.
(즉, get의 λ°˜ν™˜ νƒ€μž…μ€ μ™„λ²½ν•˜μ§€ μ•ŠμŒ).
 
λ”°λΌμ„œ μƒνƒœμ— λŒ€ν•œ νƒ€μž…μ„ μž‘μ„±ν•  ν•„μš”κ°€ μ—†λ‹€λŠ” 편의λ₯Ό μœ„ν•΄ μ•½κ°„μ˜ νƒ€μž… μ•ˆμ „μ„±μ„ μ ˆμΆ©ν•΄ combine을 ν•˜μ‹­μ‹œμ˜€.
 
λ˜ν•œ Combine은 μƒνƒœλ₯Ό "생성"ν•˜κΈ° λ•Œλ¬Έμ— Combine을 μ‚¬μš©ν•  λ•Œ 컀리 버전을 μ‚¬μš©ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
μƒνƒœλ₯Ό μƒμ„±ν•˜λŠ” 미듀웨어λ₯Ό μ‚¬μš©ν•  λ•Œ 이제 μƒνƒœλ₯Ό μœ μΆ”ν•  수 μžˆμœΌλ―€λ‘œ 컀리 버전을 μ‚¬μš©ν•  ν•„μš”κ°€ μ—†μŠ΅λ‹ˆλ‹€.
 
μƒνƒœλ₯Ό μƒμ„±ν•˜λŠ” 또 λ‹€λ₯Έ λ―Έλ“€μ›¨μ–΄λŠ” reduxμž…λ‹ˆλ‹€.
λ”°λΌμ„œ Combine, redux λ˜λŠ” μƒνƒœλ₯Ό μƒμ„±ν•˜λŠ” 기타 μ‚¬μš©μž μ •μ˜ 미듀웨어λ₯Ό μ‚¬μš©ν•  땐
컀리 버전을 μ‚¬μš©ν•˜μ§€ μ•ŠλŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

미듀웨어 μ‚¬μš©ν•˜κΈ°

λ―Έλ“€μ›¨μ–΄μ—μ„œ TypeScriptλ₯Ό μ‚¬μš©ν•˜κΈ° μœ„ν•΄ νŠΉλ³„ν•œ μž‘μ—…μ„ μˆ˜ν–‰ν•  ν•„μš”λŠ” μ—†μŠ΅λ‹ˆλ‹€.
좔둠을 μœ„ν•΄ λ‚΄λΆ€μ—μ„œ νƒ€μž…μ΄ μ‚¬μš©λ˜λ„λ‘ ν•˜μ„Έμš”

import create from 'zustand'
import { devtools, persist } from 'zustand/middleware'

interface BearState {
  bears: number
  increase: (by: number) => void
}

const useStore = create<BearState>()(
  devtools(
    persist((set) => ({
      bears: 0,
      increase: (by) => set((state) => ({ bears: state.bears + by })),
    }))
  )
)

λ°–μ—μ„œ μ‚¬μš©ν•˜λ©΄ νƒ€μž… μž…λ ₯이 μ–΄λ €μšΈ 수 μžˆμŠ΅λ‹ˆλ‹€.

import create from 'zustand'
import { devtools, persist } from 'zustand/middleware'

const myMiddlewares = (f) => devtools(persist(f))

interface BearState {
  bears: number
  increase: (by: number) => void
}

const useStore = create<BearState>()(
  myMiddlewares((set) => ({
    bears: 0,
    increase: (by) => set((state) => ({ bears: state.bears + by })),
  }))
)

미듀웨어 관리 및 κ³ κΈ‰ μ‚¬μš© 방법

이 κ°€μƒμ˜ 미듀웨어λ₯Ό μž‘μ„±ν•΄μ•Ό ν•œλ‹€κ³  μƒμƒν•΄λ³΄μ„Έμš”...

import create from 'zustand'

const foo = (f, bar) => (set, get, store) => {
  store.foo = bar
  return f(set, get, store)
}

const useStore = create(foo(() => ({ bears: 0 }), 'hello'))
console.log(useStore.foo.toUpperCase())
 
Zustand Middlewareκ°€ μŠ€ν† μ–΄λ₯Ό λ³€κ²½ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.
νƒ€μž… μˆ˜μ€€μ—μ„œ 변이λ₯Ό μ–΄λ–»κ²Œ 인코딩할 수 μžˆμŠ΅λ‹ˆκΉŒ?
즉, 이 μ½”λ“œκ°€ μ»΄νŒŒμΌλ˜λ„λ‘ fooλ₯Ό μž…λ ₯ν•˜λŠ” 방법은 λ¬΄μ—‡μž…λ‹ˆκΉŒ?
 
일반적인 μ •μ μœΌλ‘œ νƒ€μž…μ΄ μ§€μ •λœ μ–Έμ–΄μ˜ 경우 이것이 λΆˆκ°€λŠ₯ν•˜μ§€λ§Œ
TypeScript 덕뢄에 Zusstandμ—λŠ” 이λ₯Ό κ°€λŠ₯ν•˜κ²Œ ν•˜λŠ” "κ³ κΈ‰ λŒμ—°λ³€μ΄μž"λΌλŠ” 것이 μžˆμŠ΅λ‹ˆλ‹€.
미듀웨어λ₯Ό μž…λ ₯ν•˜κ±°λ‚˜ StateCreator νƒ€μž…μ„ μ‚¬μš©ν•˜λŠ” 것과 같은 λ³΅μž‘ν•œ νƒ€μž… 문제λ₯Ό μ²˜λ¦¬ν•˜λŠ” 경우
κ΅¬ν˜„ μ„ΈλΆ€ 사항을 이해해야 ν•©λ‹ˆλ‹€. (#710)
이 λ¬Έμ œμ— λŒ€ν•œ 닡이 무엇인지 μ•Œκ³  μ‹Άλ‹€λ©΄ μ—¬κΈ°(here)λ₯Ό μ°Έμ‘°ν•˜μ‹­μ‹œμ˜€.

미듀웨어 λ ˆμ‹œν”Ό

μŠ€ν† μ–΄ νƒ€μž…μ„ λ³€κ²½ν•˜μ§€ μ•ŠλŠ” 미듀웨어

import create, { State, StateCreator, StoreMutatorIdentifier } from 'zustand'

type Logger = <
  T extends State,
  Mps extends [StoreMutatorIdentifier, unknown][] = [],
  Mcs extends [StoreMutatorIdentifier, unknown][] = []
>(
  f: StateCreator<T, Mps, Mcs>,
  name?: string
) => StateCreator<T, Mps, Mcs>

type LoggerImpl = <T extends State>(
  f: PopArgument<StateCreator<T, [], []>>,
  name?: string
) => PopArgument<StateCreator<T, [], []>>

const loggerImpl: LoggerImpl = (f, name) => (set, get, store) => {
  type T = ReturnType<typeof f>
  const loggedSet: typeof set = (...a) => {
    set(...a)
    console.log(...(name ? [`${name}:`] : []), get())
  }
  store.setState = loggedSet

  return f(loggedSet, get, store)
}

export const logger = loggerImpl as unknown as Logger

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
  ...a: [...infer A, infer _]
) => infer R
  ? (...a: A) => R
  : never

// ---

const useStore = create<BearState>()(
  logger(
    (set) => ({
      bears: 0,
      increase: (by) => set((state) => ({ bears: state.bears + by })),
    }),
    'bear-store'
  )
)

μŠ€ν† μ–΄ νƒ€μž…μ„ λ³€κ²½ν•˜λŠ” 미듀웨어

import create, {
  State,
  StateCreator,
  StoreMutatorIdentifier,
  Mutate,
  StoreApi,
} from 'zustand'

type Foo = <
  T extends State,
  A,
  Mps extends [StoreMutatorIdentifier, unknown][] = [],
  Mcs extends [StoreMutatorIdentifier, unknown][] = []
>(
  f: StateCreator<T, [...Mps, ['foo', A]], Mcs>,
  bar: A
) => StateCreator<T, Mps, [['foo', A], ...Mcs]>

declare module 'zustand' {
  interface StoreMutators<S, A> {
    foo: Write<Cast<S, object>, { foo: A }>
  }
}

type FooImpl = <T extends State, A>(
  f: PopArgument<StateCreator<T, [], []>>,
  bar: A
) => PopArgument<StateCreator<T, [], []>>

const fooImpl: FooImpl = (f, bar) => (set, get, _store) => {
  type T = ReturnType<typeof f>
  type A = typeof bar

  const store = _store as Mutate<StoreApi<T>, [['foo', A]]>
  store.foo = bar
  return f(set, get, _store)
}

export const foo = fooImpl as unknown as Foo

type PopArgument<T extends (...a: never[]) => unknown> = T extends (
  ...a: [...infer A, infer _]
) => infer R
  ? (...a: A) => R
  : never

type Write<T extends object, U extends object> = Omit<T, keyof U> & U

type Cast<T, U> = T extends U ? T : U

// ---

const useStore = create(foo(() => ({ bears: 0 }), 'hello'))
console.log(useStore.foo.toUpperCase())​

컀리 없이 create μ‚¬μš©ν•˜κΈ°

createλ₯Ό μ‚¬μš©ν•˜λŠ” 데 ꢌμž₯λ˜λŠ” 방법은 μ €μž₯μ†Œ νƒ€μž…μ„ μœ μΆ”ν•  수 μžˆλ„λ‘ ν•˜κΈ° λ•Œλ¬Έμ—
create<T>()(...)와 같은 컀리가 적용된 ν•΄κ²° 방법을 μ‚¬μš©ν•˜λŠ” κ²ƒμž…λ‹ˆλ‹€.
κ·ΈλŸ¬λ‚˜ μ–΄λ–€ 이유둜 ν•΄κ²° 방법을 μ‚¬μš©ν•˜μ§€ μ•ŠμœΌλ €λ©΄ λ‹€μŒκ³Ό 같은 νƒ€μž… λ§€κ°œλ³€μˆ˜λ₯Ό 전달할 수 μžˆμŠ΅λ‹ˆλ‹€.
μ–΄λ–€ κ²½μš°μ—λŠ” 주석 λŒ€μ‹  μ–΄μ„€μ…˜μœΌλ‘œ μž‘λ™ν•˜λ―€λ‘œ ꢌμž₯ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
import create from "zustand"

interface BearState {
  bears: number
  increase: (by: number) => void
}

const useStore = create<
  BearState,
  [
    ['zustand/persist', BearState],
    ['zustand/devtools', never]
  ]
>(devtools(persist((set) => ({
  bears: 0,
  increase: (by) => set((state) => ({ bears: state.bears + by })),
})))​

독립 슬라이슀 νŒ¨ν„΄

μƒνƒœκ°€ μ„œλ‘œμ˜ 데이터λ₯Ό μ°Έμ‘°ν•˜μ§€ μ•ŠμŒ

import create, { StateCreator } from 'zustand'

interface BearSlice {
  bears: number
  addBear: () => void
}
const createBearSlice: StateCreator<BearSlice, [], []> = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
})

interface FishSlice {
  fishes: number
  addFish: () => void
}
const createFishSlice: StateCreator<FishSlice, [], []> = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

const useStore = create<BearSlice & FishSlice>()((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
}))
StateCreator<MySlice, [], []>λ₯Ό StateCreator<MySlice, Mutators, []>둜 λ°”κΏ‰λ‹ˆλ‹€.
예λ₯Ό λ“€μ–΄ devtoolsλ₯Ό μ‚¬μš©ν•˜λŠ” 경우 StateCreator<MySlice, [["zustand/devtools", never]], []>κ°€ λ©λ‹ˆλ‹€.
λ˜ν•œ StateCreator<MySlice, [], []> λŒ€μ‹  StateCreator<MySlice>λ₯Ό μž‘μ„±ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.
두 번째 및 μ„Έ 번째 λ§€κ°œλ³€μˆ˜λŠ” κΈ°λ³Έκ°’μœΌλ‘œ []λ₯Ό κ°–κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

의쑴 슬라이슀 νŒ¨ν„΄

μƒνƒœκ°€ μ„œλ‘œμ˜ 데이터λ₯Ό μ°Έμ‘°

import create, { StateCreator } from 'zustand'

interface BearSlice {
  bears: number
  addBear: () => void
  eatFish: () => void
}
const createBearSlice: StateCreator<
  BearSlice & FishSlice,
  [],
  [],
  BearSlice
> = (set) => ({
  bears: 0,
  addBear: () => set((state) => ({ bears: state.bears + 1 })),
  eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})

interface FishSlice {
  fishes: number
  addFish: () => void
}
const createFishSlice: StateCreator<
  BearSlice & FishSlice,
  [],
  [],
  FishSlice
> = (set) => ({
  fishes: 0,
  addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})

const useStore = create<BearSlice & FishSlice>()((...a) => ({
  ...createBearSlice(...a),
  ...createFishSlice(...a),
}))​
 
StateCreator<MyState, [], [], MySlice>λ₯Ό
StateCreator<MyState, Mutators, [], MySlice>둜 λ°”κΏ‰λ‹ˆλ‹€.
예λ₯Ό λ“€μ–΄ devtoolsλ₯Ό μ‚¬μš©ν•˜λŠ” 경우 StateCreator<MyState, [["zustand/devtools", Never]], [], MySlice>κ°€ λ©λ‹ˆλ‹€.
λ°˜μ‘ν˜•