Skip to content

Commit 44033bd

Browse files
committed
feat: Zustand + Immer 示例
1 parent b32dfab commit 44033bd

8 files changed

Lines changed: 92 additions & 7 deletions

File tree

package-lock.json

Lines changed: 12 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,7 @@
331331
"html2canvas": "^1.4.1",
332332
"i18next": "^25.7.3",
333333
"i18next-browser-languagedetector": "^8.2.0",
334+
"immer": "^11.1.3",
334335
"jsencrypt": "^3.5.4",
335336
"jspdf": "^3.0.4",
336337
"katex": "^0.16.27",

src/pages/zustand/index.tsx

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ const ZustandDemo = () => {
1111
const isSidebarOpen = useStore((state) => state.isSidebarOpen)
1212
const toggleSidebar = useStore((state) => state.toggleSidebar)
1313

14+
const immerItems = useStore((state) => state.immerItems)
15+
const pushImmerItem = useStore((state) => state.pushImmerItem)
16+
const popImmerItem = useStore((state) => state.popImmerItem)
17+
const clearImmerItems = useStore((state) => state.clearImmerItems)
18+
1419
return (
1520
<FixTabPanel>
1621
<div className="flex flex-col gap-6 p-6">
@@ -41,6 +46,33 @@ const ZustandDemo = () => {
4146
</div>
4247
<div className="mt-4 text-gray-500">* 此状态已配置持久化,刷新页面后状态会保持。</div>
4348
</Card>
49+
50+
<Card title="Zustand + Immer 示例">
51+
<div className="flex flex-col gap-4">
52+
<div className="flex items-center gap-6">
53+
<Statistic title="Immer 列表长度" value={immerItems.length} />
54+
<Space>
55+
<Button onClick={pushImmerItem}>Push Item (immer)</Button>
56+
<Button onClick={popImmerItem}>Pop Item</Button>
57+
<Button onClick={clearImmerItems}>Clear</Button>
58+
</Space>
59+
</div>
60+
61+
<div className="mt-2">
62+
<div className="text-sm text-gray-500">Items:</div>
63+
<div className="mt-2 max-h-40 overflow-auto">
64+
{immerItems.map((it) => (
65+
<div key={it.id} className="border-b py-1">
66+
{it.value}
67+
</div>
68+
))}
69+
</div>
70+
</div>
71+
<div className="mt-2 text-gray-500">
72+
* 这里用的是 Zustand 的 `immer` middleware:`set(draft =&gt; draft.xxx = ...)`。
73+
</div>
74+
</div>
75+
</Card>
4476
</div>
4577
</FixTabPanel>
4678
)

src/store/index.ts

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
// src/store/index.ts
22
import { create } from 'zustand'
33
import { devtools, persist } from 'zustand/middleware'
4+
import { immer } from 'zustand/middleware/immer'
45
import { createCounterSlice } from './modules/counterSlice'
56
import { createAppSlice } from './modules/appSlice'
7+
import { createImmerDemoSlice } from './modules/immerDemoSlice'
68
import { StoreState } from './types'
79

810
export const useStore = create<StoreState>()(
911
devtools(
1012
persist(
11-
(...a) => ({
12-
...createCounterSlice(...a),
13-
...createAppSlice(...a),
14-
}),
13+
immer((set, get, api) => ({
14+
...createCounterSlice(set, get, api),
15+
...createAppSlice(set, get, api),
16+
...createImmerDemoSlice(set, get, api),
17+
})),
1518
{
1619
name: 'pro-react-admin-storage',
1720
partialize: (state) => ({ isSidebarOpen: state.isSidebarOpen }),

src/store/modules/appSlice.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22
import { StateCreator } from 'zustand'
33
import { StoreState, AppSlice, AppResponsive, Screens } from '../types'
44

5-
export const createAppSlice: StateCreator<StoreState, [], [], AppSlice & AppResponsive> = (set) => ({
5+
export const createAppSlice: StateCreator<StoreState, [['zustand/immer', never]], [], AppSlice & AppResponsive> = (
6+
set
7+
) => ({
68
isSidebarOpen: true,
79
toggleSidebar: () => set((state) => ({ isSidebarOpen: !state.isSidebarOpen })),
810
setSidebarOpen: (isOpen: boolean) => set({ isSidebarOpen: isOpen }),

src/store/modules/counterSlice.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
import { StateCreator } from 'zustand'
33
import { StoreState, CounterSlice } from '../types'
44

5-
export const createCounterSlice: StateCreator<StoreState, [], [], CounterSlice> = (set, get) => ({
5+
export const createCounterSlice: StateCreator<StoreState, [['zustand/immer', never]], [], CounterSlice> = (
6+
set,
7+
get
8+
) => ({
69
count: 0,
710
increment: () => set((state) => ({ count: state.count + 1 })),
811
decrement: () => set((state) => ({ count: state.count - 1 })),
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { StateCreator } from 'zustand'
2+
import { ImmerDemoSlice, StoreState } from '../types'
3+
4+
export const createImmerDemoSlice: StateCreator<StoreState, [['zustand/immer', never]], [], ImmerDemoSlice> = (
5+
set
6+
) => ({
7+
immerItems: [],
8+
pushImmerItem: () =>
9+
set((state) => {
10+
state.immerItems.push({ id: Date.now(), value: Math.random().toFixed(4) })
11+
}),
12+
popImmerItem: () =>
13+
set((state) => {
14+
state.immerItems.pop()
15+
}),
16+
clearImmerItems: () =>
17+
set((state) => {
18+
state.immerItems = []
19+
}),
20+
})

src/store/types.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,4 +23,16 @@ export interface AppResponsive {
2323
setIsMobile: (isMobile: boolean) => void
2424
}
2525

26-
export type StoreState = CounterSlice & AppSlice & AppResponsive
26+
export interface ImmerItem {
27+
id: number
28+
value: string
29+
}
30+
31+
export interface ImmerDemoSlice {
32+
immerItems: ImmerItem[]
33+
pushImmerItem: () => void
34+
popImmerItem: () => void
35+
clearImmerItems: () => void
36+
}
37+
38+
export type StoreState = CounterSlice & AppSlice & AppResponsive & ImmerDemoSlice

0 commit comments

Comments
 (0)