Skip to content

TypeScript 指南

基本使用

使用TypeScript的区别在于,你需要写成 create()(…) (注意额外的括号以及类型参数),而不是 create(…)。其中 T 是用来注解状态的类型。例如:

import { create } from 'zustand'
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))

另外,你也可以使用 combine,它可以推断出状态,这样你就不需要去类型化它。

import { create } from 'zustand'
import { combine } from 'zustand/middleware'
const useBearStore = create(
combine({ bears: 0 }, (set) => ({
increase: (by: number) => set((state) => ({ bears: state.bears + by })),
})),
)

请注意,我们在使用 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 useBearStore = create<BearState>()(
devtools(
persist(
(set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}),
{ name: 'bearStore' },
),
),
)

只需要确保你是在 create 里面直接使用它们,以使上下文推断起作用。做一些甚至稍微复杂的事情,如下面的 myMiddlewares 会需要更高级的类型。

import { create } from 'zustand'
import { devtools, persist } from 'zustand/middleware'
const myMiddlewares = (f) => devtools(persist(f, { name: 'bearStore' }))
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<BearState>()(
myMiddlewares((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
})),
)

此外,我们建议尽可能地将 devtools 中间件放在最后。例如,当你将它与 immer 作为中间件一起使用时,它应该是 devtools(immer(…)) 而不是 immer(devtools(…))。这是因为 devtools 改变了 setState 并在其上添加了一个类型参数,如果其他中间件(如 immer)在 devtools 之前也改变了 setState,这个类型参数可能会丢失。因此,将 devtools 放在最后可以确保没有中间件在它之前改变 setState。

编写中间件和高级使用

假设你需要编写这个假设性的中间件。

import { create } from 'zustand'
const foo = (f, bar) => (set, get, store) => {
store.foo = bar
return f(set, get, store)
}
const useBearStore = create(foo(() => ({ bears: 0 }), 'hello'))
console.log(useBearStore.foo.toUpperCase())

Zustand 中间件可以改变存储。但我们如何在类型级别编码这种变化呢?也就是说,我们如何类型化 foo 使得这段代码可以编译?

对于一个通常的静态类型语言,这是不可能的。但是,由于 TypeScript,Zustand 有一个叫做 “高阶变异器” 的东西,使得这成为可能。如果你正在处理复杂的类型问题,比如类型化一个中间件或使用 StateCreator 类型,你将需要理解这个实现细节。关于这个,你可以查看 #710。

如果你急于知道这个特定问题的答案,你可以在这里看到。

常见的配方

不改变存储类型的中间件

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: StateCreator<T, [], []>,
name?: string,
) => 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())
}
const setState = store.setState
store.setState = (...a) => {
setState(...a)
console.log(...(name ? [`${name}:`] : []), store.getState())
}
return f(loggedSet, get, store)
}
export const logger = loggerImpl as unknown as Logger
// ---
const useBearStore = 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: StateCreator<T, [], []>,
bar: A,
) => 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 Write<T extends object, U extends object> = Omit<T, keyof U> & U
type Cast<T, U> = T extends U ? T : U
// ---
const useBearStore = create(foo(() => ({ bears: 0 }), 'hello'))
console.log(useBearStore.foo.toUpperCase())

不使用柯里化的 create

推荐的使用 create 的方式是使用柯里化的解决方案,像这样:create()(…)。这是因为它使你能够推断出存储类型。但是,如果出于某种原因你不想使用这个解决方案,你可以像下面这样传递类型参数。注意,在某些情况下,这作为一个断言而不是注解,所以我们不推荐它。

import { create } from "zustand"
interface BearState {
bears: number
increase: (by: number) => void
}
const useBearStore = create<
BearState,
[
['zustand/persist', BearState],
['zustand/devtools', never]
]
>(devtools(persist((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}), { name: 'bearStore' }))

切片模式

import { create, StateCreator } from 'zustand'
interface BearSlice {
bears: number
addBear: () => void
eatFish: () => void
}
interface FishSlice {
fishes: number
addFish: () => void
}
interface SharedSlice {
addBoth: () => void
getBoth: () => void
}
const createBearSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
BearSlice
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
})
const createFishSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
FishSlice
> = (set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
})
const createSharedSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
SharedSlice
> = (set, get) => ({
addBoth: () => {
// you can reuse previous methods
get().addBear()
get().addFish()
// or do them from scratch
// set((state) => ({ bears: state.bears + 1, fishes: state.fishes + 1 })
},
getBoth: () => get().bears + get().fishes,
})
const useBoundStore = create<BearSlice & FishSlice & SharedSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
...createSharedSlice(...a),
}))

关于切片模式的详细解释可以在这里找到。

如果你有一些中间件,那么将 StateCreator<MyState, [], [], MySlice> 替换为 StateCreator<MyState, Mutators, [], MySlice>。例如,如果你正在使用 devtools,那么它将是 StateCreator<MyState, [[“zustand/devtools”, never]], [], MySlice>。查看 “Middlewares and their mutators reference” 部分以获取所有变异器的列表。

为 vanilla stores 绑定 useStore 钩子

import { useStore } from 'zustand'
import { createStore } from 'zustand/vanilla'
interface BearState {
bears: number
increase: (by: number) => void
}
const bearStore = createStore<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
function useBearStore(): BearState
function useBearStore<T>(selector: (state: BearState) => T): T
function useBearStore<T>(selector?: (state: BearState) => T) {
return useStore(bearStore, selector!)
}

如果你需要经常创建绑定的 useStore 钩子并希望保持 DRY,你也可以创建一个抽象的 createBoundedUseStore 函数…

import { useStore, StoreApi } from 'zustand'
import { createStore } from 'zustand/vanilla'
interface BearState {
bears: number
increase: (by: number) => void
}
const bearStore = createStore<BearState>()((set) => ({
bears: 0,
increase: (by) => set((state) => ({ bears: state.bears + by })),
}))
const createBoundedUseStore = ((store) => (selector) => useStore(store)) as <
S extends StoreApi<unknown>,
>(
store: S,
) => {
(): ExtractState<S>
<T>(selector: (state: ExtractState<S>) => T): T
}
type ExtractState<S> = S extends { getState: () => infer X } ? X : never
const useBearStore = createBoundedUseStore(bearStore)

中间件及其变换器参考

devtools — [“zustand/devtools”, never] persist — [“zustand/persist”, YourPersistedState] YourPersistedState 是您要持久保存的状态类型,即 options.partialize 的返回类型,如果您没有传递 partialize 选项,YourPersistedState 将变为 Partial。有时传递实际的 PersistedState 不起作用。在这种情况下,请尝试传递 unknown。 immer — [“zustand/immer”, never] subscribeWithSelector — [“zustand/subscribeWithSelector”, never] redux — [“zustand/redux”, YourAction] combine — 由于 combine 不会改变存储,因此没有变换器。