00. 状态更新
Zustand 基础使用
import { create } from "zustand";
const useStore = create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
removeAllBears: () => set({ bears: 0 }),
}));
你可以在任何地方使用这个
function BearCounter() {
const bears = useStore((state) => state.bears);
return <h1>{bears} around here...</h1>;
}
function Controls() {
const increasePopulation = useStore((state) => state.increasePopulation);
return <button onClick={increasePopulation}>one up</button>;
}
推荐的用法是将行动和状态放在
export const useBoundStore = create((set) => ({
count: 0,
text: 'hello',
inc: () => set((state) => ({ count: state.count + 1 })),
setText: (text) => set({ text }),
})
这创造了一个自成一体的存储,数据和行动在一起。另一种方法是在模块层面上定义动作,在
export const useBoundStore = create(() => ({
count: 0,
text: "hello",
}));
export const inc = () =>
useBoundStore.setState((state) => ({ count: state.count + 1 }));
export const setText = (text) => useBoundStore.setState({ text });
这有几个好处:
- 它不需要一个
Hook 来调用一个动作; - 它有利于代码拆分。
虽然这种模式没有任何缺点,但由于它的封装性,有些人可能更喜欢
import { unstable_batchedUpdates } from "react-dom"; // or 'react-native'
const useFishStore = create((set) => ({
fishes: 0,
increaseFishes: () => set((prev) => ({ fishes: prev.fishes + 1 })),
}));
const nonReactCallback = () => {
unstable_batchedUpdates(() => {
useFishStore.getState().increaseFishes();
});
};
状态更新
Flat updates
用
type State = {
firstName: string
lastName: string
}
type Action = {
updateFirstName: (firstName: State['firstName']) => void
updateLastName: (lastName: State['lastName']) => void
}
// Create your store, which includes both state and (optionally) actions
const useStore = create<State & Action>((set) => ({
firstName: '',
lastName: '',
updateFirstName: (firstName) => set(() => ({ firstName: firstName })),
updateLastName: (lastName) => set(() => ({ lastName: lastName })),
}))
// In consuming app
function App() {
// "select" the needed state and actions, in this case, the firstName value
// and the action updateFirstName
const [firstName, updateFirstName] = useStore(
(state) => [state.firstName, state.updateFirstName],
shallow
)
return (
<main>
<label>
First name
<input
// Update the "firstName" state
onChange={(e) => updateFirstName(e.currentTarget.value)}
value={firstName}
/>
</label>
<p>
Hello, <strong>{firstName}!</strong>
</p>
</main>
)
}
Deeply nested object
如果你有一个像这样的深层状态对象:
type State = {
deep: {
nested: {
obj: { count: number },
},
},
};
与
normalInc: () =>
set((state) => ({
deep: {
...state.deep,
nested: {
...state.deep.nested,
obj: {
...state.deep.nested.obj,
count: state.deep.nested.obj.count + 1
}
}
}
})),
许多人使用
你可以使用
immerInc: () =>
set(produce((state: State) => { ++state.deep.nested.obj.count })),
不可变状态与合并
就像useState
一样,我们需要永恒地更新状态。下面是一个典型的例子:
import { create } from "zustand";
const useCountStore = create((set) => ({
count: 0,
inc: () => set((state) => ({ count: state.count + 1 })),
}));
set
函数是为了更新
set((state) => ({ ...state, count: state.count + 1 }));
然而,由于这是一个常见的模式,set
实际上是合并状态,我们可以跳过 ...state
部分:
set((state) => ({ count: state.count + 1 }));
set
函数只合并一个层次的状态。如果你有一个嵌套对象,你需要明确地合并它们。你会像这样使用传播操作符模式:
import { create } from "zustand";
const useCountStore = create((set) => ({
nested: { count: 0 },
inc: () =>
set((state) => ({
nested: { ...state.nested, count: state.nested.count + 1 },
})),
}));
要禁用合并行为,你可以为 set
指定一个 replace
的布尔值,像这样:
set((state) => newState, true);
针对Map 与Set 进行更新
你需要把
import { create } from "zustand";
const useFooBar = create(() => ({ foo: new Map(), bar: new Set() }));
function doSomething() {
// doing something...
// If you want to update some React component that uses `useFooBar`, you have to call setState
// to let React know that an update happened.
// Following React's best practices, you should create a new Map/Set when updating them:
useFooBar.setState((prev) => ({
foo: new Map(prev.foo).set("newKey", "newValue"),
bar: new Set(prev.bar).add("newKey"),
}));
}