此名字暂不可见
No distraction,it's you and me

zustand

2025-04-16 原神创作激励计划

Zustand

1.序:

​ 在 React 中,“状态管理”是指管理组件之间共享的数据状态的方式。简单来说,就是:

让组件知道数据的来源,并在数据发生变化时正确地更新 UI。

​ 但是,如果组件结构非常的复杂,单靠useState和props就不够用了。因此,我们需要一些额外的工具来帮助我们管理状态,比如Zustand。

2.为什么要用Zustand:

​ 很简单,因为它简单好用😋:

2.1 **简单的API **:

​ Zustand 只需要一个 create 函数来创建状态,不需要 reducer、action、dispatch、middleware 等复杂概念。

1
2
3
4
5
6
7
8
import { create } from 'zustand';

const useStore = create((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
}));

export default useStore

调用也非常简单:

1
2
const count = useStore((state) => state.count);
const increase = useStore((state) => state.increase);

​ 如果要在别的文件中使用,也不需要什么createContext,useContext之类的东西了,只需import导入即可

1
import useStore from './store'

2.2 简单的共享:

​ 不需要使用<createContext.Provider>来包裹,store全局管理状态,因此可以直接访问,而且还有各种不同的读取方法:

2.2.1 全局读取:.getState()

​ zustand 在创建 store 时,会在返回的 hook 上附带一个静态方法**.getState(),用于在 React 组件之外**,即时读取当前状态快照

注:使用 useStore.getState() 获取的状态不会触发组件的重新渲染。 如果你在组件中使用它来读取状态,即使状态发生变化,组件也不会自动更新。

1
2
3
4
5
import useStore from './store';

const currentCount = useStore.getState().count;
console.log(currentCount);

2.2.2 响应式组件:useStore(selector)

​ 在 React 组件内,直接调用 useStore(selector),zustand 只会让使用到特定状态切片的组件发生重新渲染:

1
const count = useStore(state => state.count);

2.2.3 订阅状态变化的监听器:subscribe

​ zustand 还提供了subscribe,允许在组件外或模块中监听状态变化,适用于需要在状态变化时执行特定逻辑的场景,如日志记录等。

1
2
3
4
5
6
7

const unsub1 = useStore.subscribe(console.log);


const unsub2 = useStore.subscribe((state) => state.count, (count) => {
console.log('count变化了:', count);
});

2.3 简单的异步:

​ 相对于redux原生不支持异步操作,需要导入中间件(redux-thunk),zustand直接写就对了:

1
2
3
4
5
6
7
const useStore = create((set) => ({
kfcvwo50: null,
fetchUser: async () => {
const res = await axios.get('/Thursday');
set({ kfcvwo50: res });
},
}));

2.4 简单的渲染:

​ react原生的useState在更新state时,所有使用该状态的组件都会重新渲染,哪怕组件没有实际用到改变的部分。但是zustand只有依赖特定状态的组件会重新渲染

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
import React, { useState, useContext, createContext } from 'react';

const AppContext = createContext();

const CounterProvider = ({ children }) => {
const [count, setCount] = useState(0);
const [message, setMessage] = useState('Hello');

return (
<AppContext.Provider value={{ count, setCount, message, setMessage }}>
{children}
</AppContext.Provider>
);
};

const ComponentA = () => {
const { count } = useContext(AppContext);
return <div>Component A: {count}</div>;
};

const ComponentB = () => {
const { message } = useContext(AppContext);
return <div>Component B: {message}</div>;
};

const App = () => {
const { setCount } = useContext(AppContext);

return (
<CounterProvider>
<button onClick={() => setCount((prev) => prev + 1)}>Increment Count</button>
<ComponentA />
<ComponentB />
</CounterProvider>
);
};

export default App;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import React from 'react';
import create from 'zustand';

const useStore = create((set) => ({
count: 0,
message: 'Hello',
incrementCount: () => set((state) => ({ count: state.count + 1 })),
}));

const ComponentA = () => {
const count = useStore((state) => state.count);
return <div>Component A: {count}</div>;
};

const ComponentB = () => {
const message = useStore((state) => state.message);
return <div>Component B: {message}</div>;
};

const App = () => {
const incrementCount = useStore((state) => state.incrementCount);

return (
<>
<button onClick={incrementCount}>Increment Count</button>
<ComponentA />
<ComponentB />
</>
);
};

export default App;

​ 除了上面的外,zustand还有其他优点,比如轻量级,易于集成之类的。那么,这么好用的东西怎么用呢?别急,你很急吗😡。

3.zustand的安装和使用:

安装zustand:

​ 第一步,创建一个新的React应用并且安装 Zustand 依赖, 运行下的的命令:

1
pnpm install zustand

省流版:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import { create } from 'zustand'

const useStore = create(() => ({
who: '奶龙',
}))

function App() {
const who = useStore(state => state.who)

return (
<>
Hello {who}!
</>
)
}

export default App

创建并访问store:

​ 首先,我们从 zustand 的 create() API 中传入了一个回调函数

​ 接着,回调函数返回了一个对象,也就是我们的state。如果想要使用它,就得通过create()返回的一个hook,一般叫useStore,也可以根据业务来叫,喜欢叫什么就叫什么。

​ 最后,调用 useStore() 时,Zustand 会将订阅的状态传入,由此你可解构出你需要的部分返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
import create from 'zustand'

const useStore = create(set => ({
counts:0
}))

const getCounts = useStore(state => state.counts)

return (
<div className="App">
<h1>{getCounts}</h1>
</div>
);

更新state:

​ 注意到,在使用create()的时候,回调函数里还有一个set参数,它也是一个函数,可以用来创建state,当然也可以修改状态,比如下面新增了两个方法,前者让counts的值加1,后者让counts的值减1.

1
2
3
4
5
6
const useStore = create(set => ({
count: 0,
addCounts: () => set(state => ({ counts: state.counts + 1 })),
decreaseCounts: () => set(state => ({ counts: state.count - 1 })),
}));

​ 同理,create()也能返回操作state的方法,如下:

1
2
3
4
5
6
7
8
9
10
11
const getCounts = useStore(state => state.counts)
const addCounts = useStore(state => state.addVotes);
const decreaseCounts = useStore(state => state.subtractVotes);

<div className="App">
<h1>{getCounts}</h1>
<button onClick={addCounts}>+</button>
<button onClick={decreaseCounts}>-</button>
</div>


​ 而且,正如之前所说的,zustand支持局部状态更新,哪怕你的store中包含不止一个属性,你只需要关注更新的state,其他的state自动保持原样。

1
2
3
4
5
6
const useStore = create((set) => ({
count: 0,
who: '奶龙',
addCounts: () => set(state => ({ votes: state.counts + 1 })),
subtractCounts: () => set(state => ({ votes: state.count - 1 })),
}))

​ 触发addCounts后,只有counts更新了,而奶龙不变。😋

但是state就没这么方便了:

1
2
3
4
5
6
7
8
const [store, setStore] = useState({  count: 0,Who:'奶龙' })
const addCounts = (counts) => {
setStore((prevStore) => ({
...prevStore,
counts:counts + 1
}))
}

:zustand的局部更新只适用于第一层属性,如果是嵌套对象,那就会无效,比如下面:

1
2
3
4
5
6
7
8
const useCountStore = create((set) => ({
nested: {Who: '奶龙', counts: 0 },
addCounts: () =>
set((state) => ({
nested: { counts: state.nested.counts + 1 },
})),
}))

1
2
3
4
5
6
7
8
9
10
11
12
function App() {
const nested = useCountStore(state => state.nested)
const addCounts = useCountStore(state => state.addCounts)

return (
<>
<p>{JSON.stringify(nested)}</p>
<button onClick={() => addCounts()}>addCounts</button>//点击之后,奶龙就消失了😭
</>
)
}

实际上,这样才是对的:

1
2
3
4
set((state) => ({
nested: { ...state.nested, count: state.nested.count + 1 },
}))

访问存储状态:

​ 注意到,我们使用set()来创建状态。那么根据相对论,就一定有一个get()。同样的,在参数中传入get,就可以访问存储状态了。

1
2
3
4
5
6
7
8
9
const useStore = create((set,get) => ({
counts: 0,
addCounts: () => set(state => ({ votes: state.counts + 1 })),
subtractCounts: () => set(state => ({ votes: state.count - 1 })),
action: () => {
const userCounts = get().counts
}
}));

​ 那么问题来了,既然我可以用一个变量访问state,那我还用这个get()访问存储状态干嘛呢?所以说你只看到了第二层,而你把get想成了第一层,实际上,它在大气层:

get的优点如下:

  1. 避免闭包陷阱
    如果在异步函数中不使用 get(),可能会捕获旧的状态值,从而导致状态更新不正确。get() 能让你在执行时获得最新的状态,从而避免这种问题。
  2. 简化代码逻辑
    使用 get() 不需要通过传递参数或者维护额外的中间变量,就可以即时访问当前的状态。对于某些需要依赖当前状态计算结果或条件判断的逻辑,get() 提供了一种非常直观的方式。
  3. 提高模块化和重用性
    由于 get()set() 一起被传入到 store 创建函数中,你可以把状态相关的逻辑都封装在一个函数里,这样代码更容易模块化和重用。
1
2
3
4
5
6
7
8
9
const useStore = create((set, get) => ({
count: 0,
delayedIncrement: () => {
setTimeout(() => {
const currentCount = get().count;
set({ count: currentCount + 1 });
}, 1000);
},
}));

get() 方法在 setTimeout 的回调函数执行时获取当前的 count 值,确保了状态更新的准确性。无论在调用 delayedIncrement 后的 1 秒内 count 的值如何变化,set() 都会基于最新的状态进行更新。

异步获取数据:

​ 正如之前所介绍的,zustand不需要中间件,可以直接发起fetch请求来获取异步数据:

1
2
3
4
5
6
7
8
9
10
11
12
13
const useStore = create((set,get) => ({
counts: 0,
addCounts: () => set(state => ({ votes: state.counts + 1 })),
subtractCounts: () => set(state => ({ votes: state.count - 1 })),
action: () => {
const userCounts = get().counts
}
fetch: async (url) => {
const res = await fetch(url)
set({ counts: await res.json() })
},
}));

关于中间件:

1.persist:持久化状态

​ 持久化状态是个好东西,比如在 有form的网站中, 你希望保存用户信息, 如果用户不小心刷新了页面, 就会丢失所有数据记录. 但是有了persist就不一样,persist通过 localStorage 来持久化来自应用程序的数据, 这样, 当我们刷新页面或者完全关闭页面时, 状态不会重置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { create } from 'zustand'
import { persist } from 'zustand/middleware'

const useStore = create(
persist(
(set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
reset: () => set({ count: 0 }),
}),
{
name: 'my-counter-store',
}
)
)


function Persist() {
const count = useStore((state) => state.count)
const increase = useStore((state) => state.increase)
const reset = useStore((state) => state.reset)

return (
<div>
<h1>{count}</h1>
<button onClick={increase}>+</button>
<button onClick={reset}>Reset</button>
</div>
)
}

export default Persist

2.immer:简化不可变数据更新:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
import { create } from 'zustand'
import { produce } from 'immer'

const useStore = create((set) => ({
count: 0,
todos: ['学习 Zustand'],
increase: () =>
set(
produce((state) => {
state.count += 1
})
),
addTodo: (todo: string) =>
set(
produce((state) => {
state.todos.push(todo)
})
),
}))

function Immer() {

const count = useStore((state) => state.count)
const todos = useStore((state) => state.todos)
const increase = useStore((state) => state.increase)
const addTodo = useStore((state) => state.addTodo)

return (
<div>
<h2>Count: {count}</h2>
<button onClick={increase}>+1</button>

<h3>Todos:</h3>
<ul>
{todos.map((todo, idx) => (
<li key={idx}>{todo}</li>
))}
</ul>
<button onClick={() => addTodo('新的待办项')}>添加 Todo</button>
</div>
)
}

export default Immer

​ 这里的 produce 会自动创建一个 state 的副本,你对副本(即草稿)进行修改,最后会生成一个新的状态,而原始的状态不会被直接修改。

​ produce是immer最常用的函数,除了这个之外,immer还可以导入其他的函数,比如createDraft用来创建草稿,草稿是一个原始状态的可变副本,它可以直接修改。在你修改草稿之后,可以使用 finishDraft 函数来将它转变为不可变对象;导入original 可以用来获取状态树中的原始对象。它允许你访问和比较草稿对象的原始状态,等等。

3.combine:组合多个状态片段

​ 顾名思义,combine(initialState, createFns) 就是组合。

1
2
3
4
5
6
7
8
9
10
11
12
import { create } from 'zustand'
import { combine } from 'zustand/middleware'

const useStore = create(
combine(
{ count: 0 },
(set) => ({
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
})
)
)

​ 不难看出,combine的用法非常简单,就是将值和函数合成一个完整的 store。如果是简单的全局状态管理则没必要用到combine,combine适用于实现state与 actions 分离的结构。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
// store/useAdminStore.ts
import { create } from 'zustand'
import { combine } from 'zustand/middleware'

const userSlice = combine(
{
user: null as null | { id: number; name: string },
},
(set) => ({
login: (name: string) =>
set({ user: { id: Date.now(), name } }),
logout: () => set({ user: null }),
})
)

const articleSlice = combine(
{
articles: [] as { id: number; title: string; content: string }[],
},
(set) => ({
addArticle: (title: string, content: string) =>
set((state) => ({
articles: [
...state.articles,
{ id: Date.now(), title, content },
],
})),
removeArticle: (id: number) =>
set((state) => ({
articles: state.articles.filter((a) => a.id !== id),
})),
updateArticle: (id: number, newData: { title?: string; content?: string }) =>
set((state) => ({
articles: state.articles.map((a) =>
a.id === id ? { ...a, ...newData } : a
),
})),
})
)

const uiSlice = combine(
{
isLoading: false,
activeTab: 'dashboard',
},
(set) => ({
setLoading: (loading: boolean) => set({ isLoading: loading }),
setActiveTab: (tab: string) => set({ activeTab: tab }),
})
)

const useAdminStore = create((...a) => ({
...userSlice(...a),
...articleSlice(...a),
...uiSlice(...a),
}))

export default useAdminStore

Author: John Doe

Link: https://159357254680.github.io/2025/04/16/zustand/

Copyright: All articles in this blog are licensed under CC BY-NC-SA 3.0 unless stating additionally.

NextPost >
http与WebSocket
CATALOG
  1. 1. Zustand
    1. 1.1. 1.序:
    2. 1.2. 2.为什么要用Zustand:
      1. 1.2.1. 2.1 **简单的API **:
      2. 1.2.2. 2.2 简单的共享:
        1. 1.2.2.1. 2.2.1 全局读取:.getState()
        2. 1.2.2.2. 2.2.2 响应式组件:useStore(selector)
        3. 1.2.2.3. 2.2.3 订阅状态变化的监听器:subscribe
      3. 1.2.3. 2.3 简单的异步:
      4. 1.2.4. 2.4 简单的渲染:
    3. 1.3. 3.zustand的安装和使用:
      1. 1.3.1. 安装zustand:
      2. 1.3.2. 创建并访问store:
      3. 1.3.3. 更新state:
      4. 1.3.4. 访问存储状态:
      5. 1.3.5. 异步获取数据:
      6. 1.3.6. 关于中间件:
        1. 1.3.6.1. 1.persist:持久化状态
        2. 1.3.6.2. 2.immer:简化不可变数据更新:
        3. 1.3.6.3. 3.combine:组合多个状态片段