ν΄λΉ λΌμ΄λΈλ¬λ¦¬μ 곡μλ¬Έμμμλ νμ μ€ν¬λ¦½νΈ κ΄μ μμ κ½€λ λ°°μΈ μ μ΄ λ§μ μ 리ν΄λ .
곡μλ¬Έμ λ©μΈ ν΅μ¬ λ°μ·
λ§ν¬ : https://github.com/pmndrs/zustand
μ redux λμ zustandλ₯Ό μ¬μ©νλμ?
- κ°λ¨νκ³ unopinionatedν¨
- ν μ μν μλΉμ μ£Όμν μλ¨μΌλ‘ μ¬μ©ν¨
- 컨ν μ€νΈ νλ‘λ°μ΄λκ° νμ μμ
- λ λλ§ μμ΄ κ° λ³νλ₯Ό μ»΄ν¬λνΈμ μ릴 μ μμ
μ 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
// }
- Tλ₯Ό 리ν΄νλ©΄μ Tλ₯Ό μ 곡νμ§λ§,
- Tλ get ν¨μμ λ¦¬ν΄ νμ μ ν΅ν΄μλ μ΅λλ€.
- TypeScriptλ Tλ₯Ό unknownμΌλ‘ μΆλ‘ νκ³ ν¬κΈ°ν©λλ€.
- λ¦¬ν΄ νμ μ Tλ get()μ ν΅ν΄μλ§ μ μ μλλ°,
- get()μ νμ μ Tλ‘ μΆλ‘ λ©λλ€.
- μ¦ κ³ μ°¨ νμ μΆλ‘ μ νμ©νμ§ μμ΅λλ€.
declare const createFoo: <T>(f: (t: T) => T) => T
const x = createFoo((_) => 'hello')
import create from 'zustand/vanilla'
// getμ ({foo:number}) 리ν΄
const useStore = create<{ foo: number }>()((_, get) => ({
foo: get().foo,
}))
μ¬μ€ μ΄κΈ° μνκ° μμ±λκΈ° μ μ getμ 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())
}
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())
}
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 μ¬μ© μ μ‘°μ¬νμΈμ...
λ―Έλ€μ¨μ΄ μ¬μ©νκΈ°
λ―Έλ€μ¨μ΄μμ 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())
λ―Έλ€μ¨μ΄ λ μνΌ
μ€ν μ΄ νμ μ λ³κ²½νμ§ μλ λ―Έλ€μ¨μ΄
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 μ¬μ©νκΈ°
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),
}))
μμ‘΄ μ¬λΌμ΄μ€ ν¨ν΄
μνκ° μλ‘μ λ°μ΄ν°λ₯Ό μ°Έμ‘°
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),
}))β
'FrontEnd' μΉ΄ν κ³ λ¦¬μ λ€λ₯Έ κΈ
React DnD νν λ¦¬μΌ (0) | 2022.06.30 |
---|---|
CSS : Flexμ min-width (0) | 2022.06.30 |
React-Hook-Formsλ‘ μ¬μ¬μ© κ°λ₯ν νΌ λ§λ€κΈ° (0) | 2022.06.28 |
리μ‘νΈ λΌμ°ν° v6λ₯Ό μ΄μ©ν΄ μ½κ² λͺ¨λ¬ λ§λ€κΈ° (0) | 2022.06.27 |
리μ‘νΈ ν μ€νΈ : νΌ(Form) ν μ€νΈ (0) | 2022.06.26 |