乐闻世界logo
搜索文章和话题

How to manage state in Next.js?

2月17日 23:32

Next.js provides multiple state management solutions, and developers can choose the appropriate approach based on project requirements. Here are the commonly used state management methods in Next.js:

1. React Built-in State Management

useState Hook

Used for managing component local state.

javascript
'use client'; import { useState } from 'react'; export default function Counter() { const [count, setCount] = useState(0); return ( <div> <p>Count: {count}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> </div> ); }

useReducer Hook

Used for managing complex state logic.

javascript
'use client'; import { useReducer } from 'react'; const initialState = { count: 0 }; function reducer(state, action) { switch (action.type) { case 'increment': return { count: state.count + 1 }; case 'decrement': return { count: state.count - 1 }; default: throw new Error(); } } export default function Counter() { const [state, dispatch] = useReducer(reducer, initialState); return ( <div> <p>Count: {state.count}</p> <button onClick={() => dispatch({ type: 'increment' })}>+</button> <button onClick={() => dispatch({ type: 'decrement' })}>-</button> </div> ); }

useContext Hook

Used for sharing state across components.

javascript
'use client'; import { createContext, useContext, useState } from 'react'; const ThemeContext = createContext(); export function ThemeProvider({ children }) { const [theme, setTheme] = useState('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; } // Usage export default function App() { return ( <ThemeProvider> <Header /> <Content /> </ThemeProvider> ); } function Header() { const { theme, setTheme } = useTheme(); return ( <header> <button onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}> Toggle Theme </button> </header> ); }

2. Global State Management Libraries

Zustand

Lightweight, simple state management library.

javascript
// store/useStore.js import { create } from 'zustand'; export const useStore = create((set) => ({ count: 0, increment: () => set((state) => ({ count: state.count + 1 })), decrement: () => set((state) => ({ count: state.count - 1 })), reset: () => set({ count: 0 }), })); // Usage 'use client'; import { useStore } from '@/store/useStore'; export default function Counter() { const { count, increment, decrement, reset } = useStore(); return ( <div> <p>Count: {count}</p> <button onClick={increment}>+</button> <button onClick={decrement}>-</button> <button onClick={reset}>Reset</button> </div> ); }

Redux Toolkit

Powerful state management library suitable for large applications.

javascript
// store/slices/counterSlice.js import { createSlice } from '@reduxjs/toolkit'; export const counterSlice = createSlice({ name: 'counter', initialState: { value: 0, }, reducers: { increment: (state) => { state.value += 1; }, decrement: (state) => { state.value -= 1; }, incrementByAmount: (state, action) => { state.value += action.payload; }, }, }); export const { increment, decrement, incrementByAmount } = counterSlice.actions; export default counterSlice.reducer; // store/index.js import { configureStore } from '@reduxjs/toolkit'; import counterReducer from './slices/counterSlice'; export const store = configureStore({ reducer: { counter: counterReducer, }, }); // store/hooks.js import { useDispatch, useSelector } from 'react-redux'; import type { TypedUseSelectorHook } from 'react-redux'; import type { RootState, AppDispatch } from './index'; export const useAppDispatch: () => AppDispatch = useDispatch; export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector; // Usage 'use client'; import { useAppDispatch, useAppSelector } from '@/store/hooks'; import { increment, decrement, incrementByAmount } from '@/store/slices/counterSlice'; export default function Counter() { const count = useAppSelector((state) => state.counter.value); const dispatch = useAppDispatch(); return ( <div> <p>Count: {count}</p> <button onClick={() => dispatch(increment())}>+</button> <button onClick={() => dispatch(decrement())}>-</button> <button onClick={() => dispatch(incrementByAmount(10))}>+10</button> </div> ); }

Jotai

Atomic state management, similar to Recoil.

javascript
// store/atoms.js import { atom } from 'jotai'; export const countAtom = atom(0); export const doubledCountAtom = atom((get) => get(countAtom) * 2); // Usage 'use client'; import { useAtom } from 'jotai'; import { countAtom, doubledCountAtom } from '@/store/atoms'; export default function Counter() { const [count, setCount] = useAtom(countAtom); const [doubledCount] = useAtom(doubledCountAtom); return ( <div> <p>Count: {count}</p> <p>Doubled: {doubledCount}</p> <button onClick={() => setCount(c => c + 1)}>Increment</button> </div> ); }

3. Server State Management

SWR

Used for data fetching and caching.

javascript
'use client'; import useSWR from 'swr'; const fetcher = (url) => fetch(url).then((res) => res.json()); export default function UserProfile() { const { data, error, isLoading } = useSWR('/api/user', fetcher); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error</div>; return ( <div> <h1>{data.name}</h1> <p>{data.email}</p> </div> ); }

React Query (TanStack Query)

Powerful data fetching and state management library.

javascript
'use client'; import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'; async function fetchUser() { const res = await fetch('/api/user'); return res.json(); } async function updateUser(data) { const res = await fetch('/api/user', { method: 'PUT', body: JSON.stringify(data), }); return res.json(); } export default function UserProfile() { const queryClient = useQueryClient(); const { data, isLoading, error } = useQuery({ queryKey: ['user'], queryFn: fetchUser, }); const mutation = useMutation({ mutationFn: updateUser, onSuccess: () => { queryClient.invalidateQueries({ queryKey: ['user'] }); }, }); if (isLoading) return <div>Loading...</div>; if (error) return <div>Error</div>; return ( <div> <h1>{data.name}</h1> <p>{data.email}</p> <button onClick={() => mutation.mutate({ name: 'New Name' })}> Update Name </button> </div> ); }

4. Form State Management

React Hook Form

High-performance form state management.

javascript
'use client'; import { useForm } from 'react-hook-form'; export default function ContactForm() { const { register, handleSubmit, formState: { errors } } = useForm(); const onSubmit = (data) => { console.log(data); }; return ( <form onSubmit={handleSubmit(onSubmit)}> <div> <label>Name</label> <input {...register('name', { required: true })} /> {errors.name && <span>This field is required</span>} </div> <div> <label>Email</label> <input {...register('email', { required: true, pattern: /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$/i })} /> {errors.email && <span>Invalid email</span>} </div> <button type="submit">Submit</button> </form> ); }

5. URL State Management

Using URL Query Parameters

javascript
'use client'; import { useRouter, useSearchParams } from 'next/navigation'; export default function ProductList() { const router = useRouter(); const searchParams = useSearchParams(); const page = parseInt(searchParams.get('page') || '1'); const category = searchParams.get('category') || 'all'; const handlePageChange = (newPage) => { const params = new URLSearchParams(searchParams.toString()); params.set('page', newPage.toString()); router.push(`?${params.toString()}`); }; const handleCategoryChange = (newCategory) => { const params = new URLSearchParams(searchParams.toString()); params.set('category', newCategory); params.set('page', '1'); router.push(`?${params.toString()}`); }; return ( <div> <select value={category} onChange={(e) => handleCategoryChange(e.target.value)} > <option value="all">All</option> <option value="electronics">Electronics</option> <option value="clothing">Clothing</option> </select> <div>Current page: {page}</div> <button onClick={() => handlePageChange(page - 1)}>Previous</button> <button onClick={() => handlePageChange(page + 1)}>Next</button> </div> ); }

6. Server Component State

Using Server Components

javascript
// Server components don't need client-side state management async function ProductList() { const products = await fetch('https://api.example.com/products', { next: { revalidate: 3600 } }).then(r => r.json()); return ( <div> {products.map(product => ( <ProductCard key={product.id} product={product} /> ))} </div> ); }

State Management Best Practices

  1. Choose the right tool:

    • Simple state: useState, useReducer
    • Cross-component state: useContext
    • Global state: Zustand, Redux Toolkit
    • Server state: SWR, React Query
    • Form state: React Hook Form
  2. Minimize state: Only store necessary state, derive other state

  3. Server-first: Use server components as much as possible to reduce client-side state

  4. Avoid over-engineering: Don't introduce complex state management libraries for simple state

  5. Type safety: Use TypeScript to ensure type safety

  6. Performance optimization: Use React.memo, useMemo, useCallback to optimize performance

  7. Persistence: Use localStorage or IndexedDB to persist important state

By properly choosing and using these state management methods, you can build efficient and maintainable Next.js applications.

标签:Next.js