如何集成状态管理库(如 Zustand)?在 RSC 环境中需要注意什么?
Zustand 集成
1. 基础设置
# 安装 Zustand
npm install zustand
// store/useStore.js
import { create } from 'zustand'
import { devtools } from 'zustand/middleware'
const useStore = create(
devtools(
(set, get) => ({
// 状态
count: 0,
user: null,
theme: 'light',
// 操作
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
setUser: (user) => set({ user }),
setTheme: (theme) => set({ theme }),
// 异步操作
fetchUser: async (id) => {
try {
const response = await fetch(`/api/users/${id}`)
const user = await response.json()
set({ user })
} catch (error) {
console.error('Failed to fetch user:', error)
}
},
}),
{
name: 'app-store', // 用于 Redux DevTools
}
)
)
export default useStore
2. 在 Client Components 中使用
// app/components/Counter.jsx
'use client'
import useStore from '@/store/useStore'
export default function Counter() {
const { count, increment, decrement } = useStore()
return (
<div>
<h2>Count: {count}</h2>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
)
}
// app/components/UserProfile.jsx
'use client'
import { useEffect } from 'react'
import useStore from '@/store/useStore'
export default function UserProfile() {
const { user, fetchUser } = useStore()
useEffect(() => {
fetchUser(1)
}, [fetchUser])
if (!user) {
return <div>Loading...</div>
}
return (
<div>
<h2>{user.name}</h2>
<p>{user.email}</p>
</div>
)
}
RSC 环境中的注意事项
1. 不能在 Server Components 中使用
// ❌ 错误 - 在 Server Component 中使用 Zustand
// app/page.js
import useStore from '@/store/useStore'
export default function HomePage() {
// 错误:Server Components 不能使用客户端状态
const { count } = useStore()
return (
<div>
<h1>Count: {count}</h1>
</div>
)
}
// ✅ 正确 - 使用 Client Component
// app/components/ClientCounter.jsx
'use client'
import useStore from '@/store/useStore'
export default function ClientCounter() {
const { count } = useStore()
return (
<div>
<h1>Count: {count}</h1>
</div>
)
}
// app/page.js
import ClientCounter from '@/components/ClientCounter'
export default function HomePage() {
return (
<div>
<h1>Home Page</h1>
<ClientCounter />
</div>
)
}
2. 服务器端数据初始化
// app/page.js - Server Component
import { db } from '@/lib/database'
import ClientCounter from '@/components/ClientCounter'
export default async function HomePage() {
// 在服务器端获取数据
const initialData = await db.counters.findFirst()
return (
<div>
<h1>Home Page</h1>
<ClientCounter initialCount={initialData?.count || 0} />
</div>
)
}
// app/components/ClientCounter.jsx
'use client'
import { useEffect } from 'react'
import useStore from '@/store/useStore'
export default function ClientCounter({ initialCount }) {
const { count, setCount } = useStore()
// 使用服务器端数据初始化客户端状态
useEffect(() => {
if (initialCount !== undefined) {
setCount(initialCount)
}
}, [initialCount, setCount])
return (
<div>
<h2>Count: {count}</h2>
</div>
)
}
3. 状态持久化
// store/useStore.js
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useStore = create(
persist(
(set, get) => ({
count: 0,
user: null,
theme: 'light',
increment: () => set((state) => ({ count: state.count + 1 })),
setUser: (user) => set({ user }),
setTheme: (theme) => set({ theme }),
}),
{
name: 'app-storage', // localStorage 键名
partialize: (state) => ({
count: state.count,
theme: state.theme,
}), // 只持久化部分状态
}
)
)
export default useStore
实际应用示例
1. 购物车状态管理
// store/useCartStore.js
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useCartStore = create(
persist(
(set, get) => ({
items: [],
total: 0,
addItem: (product) => {
const items = get().items
const existingItem = items.find((item) => item.id === product.id)
if (existingItem) {
set({
items: items.map((item) =>
item.id === product.id
? { ...item, quantity: item.quantity + 1 }
: item
),
})
} else {
set({
items: [...items, { ...product, quantity: 1 }],
})
}
get().calculateTotal()
},
removeItem: (productId) => {
set({
items: get().items.filter((item) => item.id !== productId),
})
get().calculateTotal()
},
updateQuantity: (productId, quantity) => {
if (quantity <= 0) {
get().removeItem(productId)
return
}
set({
items: get().items.map((item) =>
item.id === productId ? { ...item, quantity } : item
),
})
get().calculateTotal()
},
calculateTotal: () => {
const total = get().items.reduce(
(sum, item) => sum + item.price * item.quantity,
0
)
set({ total })
},
clearCart: () => set({ items: [], total: 0 }),
}),
{
name: 'cart-storage',
}
)
)
export default useCartStore
// app/components/Cart.jsx
'use client'
import useCartStore from '@/store/useCartStore'
export default function Cart() {
const { items, total, removeItem, updateQuantity, clearCart } = useCartStore()
return (
<div>
<h2>Shopping Cart</h2>
{items.length === 0 ? (
<p>Your cart is empty</p>
) : (
<>
{items.map((item) => (
<div key={item.id}>
<h3>{item.name}</h3>
<p>Price: ${item.price}</p>
<div>
<button
onClick={() => updateQuantity(item.id, item.quantity - 1)}
>
-
</button>
<span>{item.quantity}</span>
<button
onClick={() => updateQuantity(item.id, item.quantity + 1)}
>
+
</button>
</div>
<button onClick={() => removeItem(item.id)}>Remove</button>
</div>
))}
<div>
<h3>Total: ${total}</h3>
<button onClick={clearCart}>Clear Cart</button>
</div>
</>
)}
</div>
)
}
2. 用户认证状态
// store/useAuthStore.js
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useAuthStore = create(
persist(
(set, get) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (credentials) => {
try {
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(credentials),
})
if (response.ok) {
const { user, token } = await response.json()
set({ user, token, isAuthenticated: true })
return { success: true }
} else {
return { success: false, error: 'Invalid credentials' }
}
} catch (error) {
return { success: false, error: error.message }
}
},
logout: () => {
set({ user: null, token: null, isAuthenticated: false })
},
updateUser: (userData) => {
set({ user: { ...get().user, ...userData } })
},
}),
{
name: 'auth-storage',
partialize: (state) => ({
user: state.user,
token: state.token,
isAuthenticated: state.isAuthenticated,
}),
}
)
)
export default useAuthStore
// app/components/AuthButton.jsx
'use client'
import useAuthStore from '@/store/useAuthStore'
export default function AuthButton() {
const { user, isAuthenticated, logout } = useAuthStore()
if (!isAuthenticated) {
return <a href="/login">Login</a>
}
return (
<div>
<span>Welcome, {user.name}</span>
<button onClick={logout}>Logout</button>
</div>
)
}
高级功能
1. 中间件组合
// store/useAdvancedStore.js
import { create } from 'zustand'
import { devtools, persist, subscribeWithSelector } from 'zustand/middleware'
const useAdvancedStore = create(
devtools(
persist(
subscribeWithSelector((set, get) => ({
count: 0,
user: null,
increment: () => set((state) => ({ count: state.count + 1 })),
setUser: (user) => set({ user }),
})),
{
name: 'advanced-storage',
}
),
{
name: 'advanced-store',
}
)
)
// 订阅状态变化
useAdvancedStore.subscribe(
(state) => state.count,
(count) => {
console.log('Count changed:', count)
}
)
export default useAdvancedStore
2. 类型安全
// store/types.ts
export interface User {
id: string
name: string
email: string
}
export interface AuthState {
user: User | null
token: string | null
isAuthenticated: boolean
login: (credentials: LoginCredentials) => Promise<LoginResult>
logout: () => void
updateUser: (userData: Partial<User>) => void
}
export interface LoginCredentials {
email: string
password: string
}
export interface LoginResult {
success: boolean
error?: string
}
// store/useAuthStore.ts
import { create } from 'zustand'
import { AuthState, LoginCredentials, LoginResult } from './types'
const useAuthStore = create<AuthState>((set, get) => ({
user: null,
token: null,
isAuthenticated: false,
login: async (credentials: LoginCredentials): Promise<LoginResult> => {
// 实现登录逻辑
},
logout: () => {
set({ user: null, token: null, isAuthenticated: false })
},
updateUser: (userData: Partial<User>) => {
const currentUser = get().user
if (currentUser) {
set({ user: { ...currentUser, ...userData } })
}
},
}))
export default useAuthStore
最佳实践
1. 状态分离
// store/useUserStore.js
const useUserStore = create((set) => ({
user: null,
setUser: (user) => set({ user }),
}))
// store/useCartStore.js
const useCartStore = create((set) => ({
items: [],
addItem: (item) => set((state) => ({ items: [...state.items, item] })),
}))
// store/useThemeStore.js
const useThemeStore = create((set) => ({
theme: 'light',
setTheme: (theme) => set({ theme }),
}))
2. 性能优化
// 使用选择器避免不必要的重渲染
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)
// 使用 shallow 比较
import { shallow } from 'zustand/shallow'
const { count, increment } = useStore(
(state) => ({ count: state.count, increment: state.increment }),
shallow
)
总结
在 Next.js 中集成状态管理库的要点:
Zustand 集成:
- 基础设置和配置
- 中间件使用
- 类型安全
RSC 环境注意事项:
- 不能在 Server Components 中使用
- 服务器端数据初始化
- 状态持久化
最佳实践:
- 状态分离
- 性能优化
- 类型安全
- 错误处理
推荐方案:
- Zustand (轻量级)
- Redux Toolkit (复杂应用)
- Jotai (原子化状态)