Next.js 状态管理 🗂️
🎯 概述
状态管理是构建复杂应用的关键。Next.js 支持多种状态管理方案,包括 React Context、Zustand、Redux 等,适用于不同规模的应用。
📦 状态管理方案
1. React Context (内置)
- 适合:小到中型应用
- 优点:无需额外依赖
- 缺点:性能优化较复杂
2. Zustand (推荐)
- 适合:中到大型应用
- 优点:简单、高性能
- 缺点:生态相对较小
3. Redux Toolkit
- 适合:大型企业应用
- 优点:成熟、生态丰富
- 缺点:学习曲线陡峭
🚀 React Context
基本用法
tsx
// context/ThemeContext.tsx
'use client'
import { createContext, useContext, useState } from 'react'
type Theme = 'light' | 'dark'
interface ThemeContextType {
theme: Theme
setTheme: (theme: Theme) => void
}
const ThemeContext = createContext<ThemeContextType | undefined>(undefined)
export function ThemeProvider({ children }: { children: React.ReactNode }) {
const [theme, setTheme] = useState<Theme>('light')
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
)
}
export function useTheme() {
const context = useContext(ThemeContext)
if (!context) {
throw new Error('useTheme must be used within ThemeProvider')
}
return context
}使用:
tsx
// app/layout.tsx
import { ThemeProvider } from '@/context/ThemeContext'
export default function RootLayout({ children }) {
return (
<html>
<body>
<ThemeProvider>
{children}
</ThemeProvider>
</body>
</html>
)
}
// components/ThemeToggle.tsx
'use client'
import { useTheme } from '@/context/ThemeContext'
export default function ThemeToggle() {
const { theme, setTheme } = useTheme()
return (
<button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}>
切换到 {theme === 'light' ? '暗色' : '亮色'} 模式
</button>
)
}📝 Zustand
安装
bash
npm install zustand创建 Store
tsx
// store/useStore.ts
import { create } from 'zustand'
interface User {
id: string
name: string
email: string
}
interface StoreState {
user: User | null
setUser: (user: User) => void
clearUser: () => void
count: number
increment: () => void
decrement: () => void
}
export const useStore = create<StoreState>((set) => ({
user: null,
setUser: (user) => set({ user }),
clearUser: () => set({ user: null }),
count: 0,
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}))使用 Store
tsx
// components/Counter.tsx
'use client'
import { useStore } from '@/store/useStore'
export default function Counter() {
const count = useStore((state) => state.count)
const increment = useStore((state) => state.increment)
const decrement = useStore((state) => state.decrement)
return (
<div>
<p>计数: {count}</p>
<button onClick={increment}>+1</button>
<button onClick={decrement}>-1</button>
</div>
)
}持久化存储
tsx
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
export const useStore = create(
persist(
(set) => ({
user: null,
setUser: (user) => set({ user }),
}),
{
name: 'user-storage',
}
)
)🎨 Redux Toolkit
安装
bash
npm install @reduxjs/toolkit react-redux创建 Store
tsx
// store/store.ts
import { configureStore } from '@reduxjs/toolkit'
import userReducer from './userSlice'
import cartReducer from './cartSlice'
export const store = configureStore({
reducer: {
user: userReducer,
cart: cartReducer,
},
})
export type RootState = ReturnType<typeof store.getState>
export type AppDispatch = typeof store.dispatch创建 Slice
tsx
// store/userSlice.ts
import { createSlice, PayloadAction } from '@reduxjs/toolkit'
interface User {
id: string
name: string
email: string
}
interface UserState {
user: User | null
loading: boolean
}
const initialState: UserState = {
user: null,
loading: false,
}
export const userSlice = createSlice({
name: 'user',
initialState,
reducers: {
setUser: (state, action: PayloadAction<User>) => {
state.user = action.payload
},
clearUser: (state) => {
state.user = null
},
setLoading: (state, action: PayloadAction<boolean>) => {
state.loading = action.payload
},
},
})
export const { setUser, clearUser, setLoading } = userSlice.actions
export default userSlice.reducerProvider 设置
tsx
// app/providers.tsx
'use client'
import { Provider } from 'react-redux'
import { store } from '@/store/store'
export function Providers({ children }: { children: React.ReactNode }) {
return <Provider store={store}>{children}</Provider>
}
// app/layout.tsx
import { Providers } from './providers'
export default function RootLayout({ children }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}使用 Redux
tsx
// components/UserProfile.tsx
'use client'
import { useSelector, useDispatch } from 'react-redux'
import { RootState } from '@/store/store'
import { setUser, clearUser } from '@/store/userSlice'
export default function UserProfile() {
const user = useSelector((state: RootState) => state.user.user)
const dispatch = useDispatch()
const handleLogin = () => {
dispatch(setUser({ id: '1', name: '张三', email: 'zhang@example.com' }))
}
const handleLogout = () => {
dispatch(clearUser())
}
return (
<div>
{user ? (
<div>
<p>欢迎,{user.name}</p>
<button onClick={handleLogout}>退出</button>
</div>
) : (
<button onClick={handleLogin}>登录</button>
)}
</div>
)
}📊 实战示例
购物车管理 (Zustand)
tsx
// store/useCartStore.ts
import { create } from 'zustand'
interface CartItem {
id: string
name: string
price: number
quantity: number
}
interface CartStore {
items: CartItem[]
addItem: (item: Omit<CartItem, 'quantity'>) => void
removeItem: (id: string) => void
updateQuantity: (id: string, quantity: number) => void
clearCart: () => void
total: () => number
}
export const useCartStore = create<CartStore>((set, get) => ({
items: [],
addItem: (item) => set((state) => {
const existing = state.items.find(i => i.id === item.id)
if (existing) {
return {
items: state.items.map(i =>
i.id === item.id ? { ...i, quantity: i.quantity + 1 } : i
)
}
}
return { items: [...state.items, { ...item, quantity: 1 }] }
}),
removeItem: (id) => set((state) => ({
items: state.items.filter(i => i.id !== id)
})),
updateQuantity: (id, quantity) => set((state) => ({
items: state.items.map(i =>
i.id === id ? { ...i, quantity } : i
)
})),
clearCart: () => set({ items: [] }),
total: () => {
const { items } = get()
return items.reduce((sum, item) => sum + item.price * item.quantity, 0)
},
}))使用:
tsx
// components/Cart.tsx
'use client'
import { useCartStore } from '@/store/useCartStore'
export default function Cart() {
const items = useCartStore((state) => state.items)
const removeItem = useCartStore((state) => state.removeItem)
const total = useCartStore((state) => state.total())
return (
<div>
<h2>购物车</h2>
{items.map(item => (
<div key={item.id}>
<span>{item.name}</span>
<span>{item.price}元 x {item.quantity}</span>
<button onClick={() => removeItem(item.id)}>删除</button>
</div>
))}
<p>总计: {total}元</p>
</div>
)
}用户认证 (Context)
tsx
// context/AuthContext.tsx
'use client'
import { createContext, useContext, useState, useEffect } from 'react'
interface User {
id: string
name: string
email: string
}
interface AuthContextType {
user: User | null
login: (email: string, password: string) => Promise<void>
logout: () => void
loading: boolean
}
const AuthContext = createContext<AuthContextType | undefined>(undefined)
export function AuthProvider({ children }: { children: React.ReactNode }) {
const [user, setUser] = useState<User | null>(null)
const [loading, setLoading] = useState(true)
useEffect(() => {
// 检查本地存储的 token
const token = localStorage.getItem('token')
if (token) {
fetchUser(token)
} else {
setLoading(false)
}
}, [])
const fetchUser = async (token: string) => {
try {
const res = await fetch('/api/user', {
headers: { Authorization: `Bearer ${token}` }
})
const data = await res.json()
setUser(data)
} catch (error) {
console.error('获取用户失败:', error)
} finally {
setLoading(false)
}
}
const login = async (email: string, password: string) => {
const res = await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password })
})
const { token, user } = await res.json()
localStorage.setItem('token', token)
setUser(user)
}
const logout = () => {
localStorage.removeItem('token')
setUser(null)
}
return (
<AuthContext.Provider value={{ user, login, logout, loading }}>
{children}
</AuthContext.Provider>
)
}
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}📚 最佳实践
1. 选择合适的状态管理方案
tsx
// 本地状态 - useState
function Component() {
const [open, setOpen] = useState(false)
return <div>{/* ... */}</div>
}
// 组件间共享 - Context
<ThemeProvider>
<App />
</ThemeProvider>
// 全局状态 - Zustand/Redux
const user = useStore((state) => state.user)2. 避免过度使用全局状态
tsx
// ❌ 不推荐:所有状态都放全局
const formData = useStore((state) => state.formData)
// ✅ 推荐:表单状态保持本地
const [formData, setFormData] = useState({})3. 使用选择器优化性能
tsx
// ❌ 不推荐:订阅整个 store
const store = useStore()
// ✅ 推荐:只订阅需要的数据
const user = useStore((state) => state.user)
const count = useStore((state) => state.count)🔗 相关资源
下一步:学习 Next.js 身份验证。