2025 React 狀態(tài)管理終極指南!
React 作為當(dāng)下最受歡迎的前端框架,在構(gòu)建復(fù)雜且交互豐富的應(yīng)用時(shí),狀態(tài)管理無疑是至關(guān)重要的一環(huán)。從簡(jiǎn)單的本地狀態(tài),到能讓多個(gè)組件協(xié)同工作的全局狀態(tài),再到涉及服務(wù)器通信、導(dǎo)航切換、表單操作以及持久化存儲(chǔ)等不同場(chǎng)景下的狀態(tài)管理,每一個(gè)方面都影響著應(yīng)用的性能、用戶體驗(yàn)以及可維護(hù)性。本文將作為 React 狀態(tài)管理的全面指南,帶你深入了解這些不同類型狀態(tài)的管理方式與要點(diǎn)。
圖片
本地狀態(tài)
- 定義: 在 React 中,本地狀態(tài)是指組件內(nèi)部管理的數(shù)據(jù),這些數(shù)據(jù)可以影響組件的渲染輸出和行為。本地狀態(tài)是相對(duì)于全局狀態(tài)而言的,它只存在于單個(gè)組件中,用于存儲(chǔ)那些不需要在整個(gè)應(yīng)用范圍內(nèi)共享的信息。
- 特點(diǎn):
- 私有性:本地狀態(tài)是特定于某個(gè)組件的,其他組件無法直接訪問或修改它。
- 局部性:狀態(tài)的變化只會(huì)影響該組件及其子組件,而不會(huì)影響到父組件或其他兄弟組件。
生命周期性:隨著組件的掛載、更新和卸載,本地狀態(tài)也會(huì)經(jīng)歷相應(yīng)的生命周期階段。
函數(shù)組件
useState
從 React 16.8 開始引入了 Hooks API,使得函數(shù)組件也可以擁有狀態(tài)。useState 是最常用的 Hook 之一,用來聲明和管理組件的狀態(tài)。
- 返回值:調(diào)用 useState 時(shí),返回一個(gè)包含兩個(gè)元素的數(shù)組。第一個(gè)元素是當(dāng)前的狀態(tài)值,可以在組件中直接使用來渲染 UI 等;第二個(gè)元素是一個(gè)函數(shù),用于更新該狀態(tài)值,調(diào)用這個(gè)函數(shù)并傳入新的值后,React 會(huì)重新渲染組件,使 UI 根據(jù)新的狀態(tài)進(jìn)行更新。
import React, { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>當(dāng)前計(jì)數(shù): {count}</p>
<button onClick={() => setCount(count + 1)}>
Click me
</button>
</div>
);
}useReducer
對(duì)于那些涉及復(fù)雜狀態(tài)邏輯或狀態(tài)更新依賴于前一狀態(tài)的情況,useReducer 可能是一個(gè)更好的選擇。它類似于 Redux 的 reducer 函數(shù),但只作用于單個(gè)組件內(nèi)部。
- reducer 函數(shù):它是整個(gè)狀態(tài)管理的核心邏輯部分,根據(jù)傳入的不同 action 類型,按照預(yù)先定義好的規(guī)則來計(jì)算并返回新的狀態(tài),就像一個(gè)狀態(tài)處理的 “加工廠”,規(guī)范了狀態(tài)更新的流程。
- dispatch 函數(shù):通過調(diào)用 dispatch 并傳入相應(yīng)的 action 對(duì)象,可以觸發(fā) reducer 函數(shù)執(zhí)行,從而實(shí)現(xiàn)狀態(tài)的更新。這使得狀態(tài)更新的操作更加可預(yù)測(cè)、可維護(hù),尤其是在復(fù)雜的狀態(tài)變化場(chǎng)景中優(yōu)勢(shì)明顯。
import React, { 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();
}
}
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>
);
}自定義Hooks
可以創(chuàng)建自定義 Hook 來封裝特定的狀態(tài)邏輯,并且可以在多個(gè)組件之間共享這些邏輯。這不僅提高了代碼的復(fù)用性,還使得狀態(tài)管理更加模塊化。
import { useState, useEffect } from 'react';
import axios from 'axios';
const useFetchData = (url, params) => {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [error, setError] = useState(null);
useEffect(() => {
const fetchData = async () => {
try {
const response = await axios.get(url, { params });
setData(response.data);
} catch (err) {
setError(err.message);
} finally {
setIsLoading(false);
}
};
fetchData();
}, [url, params]); // 依賴項(xiàng)包括 URL 和參數(shù),以便在它們變化時(shí)重新獲取數(shù)據(jù)
return { data, isLoading, error };
};
export default useFetchData;類組件
在 Hooks 出現(xiàn)之前,React 類組件通過定義 state 對(duì)象并在構(gòu)造函數(shù)中初始化它來管理狀態(tài)。
class Counter extends React.Component {
constructor(props) {
super(props);
this.state = { count: 0 };
}
increment = () => {
this.setState((prevState) => ({ count: prevState.count + 1 }));
};
render() {
return (
<div>
<p>當(dāng)前計(jì)數(shù): {this.state.count}</p>
<button onClick={this.increment}>
Click me
</button>
</div>
);
}
}注意事項(xiàng):
- 初始化:通常在類組件的構(gòu)造函數(shù) constructor 中通過給 this.state 賦值來初始化本地狀態(tài),設(shè)定初始的狀態(tài)值。
- 更新狀態(tài):使用 this.setState 方法來更新狀態(tài),它有兩種常見的使用形式。一種是直接傳入一個(gè)包含新狀態(tài)值的對(duì)象,如 this.setState({ count: 10 });另一種是傳入一個(gè)函數(shù),函數(shù)接收前一個(gè)狀態(tài) prevState 作為參數(shù),返回新的狀態(tài)對(duì)象,這種方式在基于前一狀態(tài)進(jìn)行計(jì)算來更新狀態(tài)時(shí)非常有用,能避免一些由于異步操作等帶來的狀態(tài)更新問題。
全局狀態(tài)
- 定義: 在 React 中,全局狀態(tài)是指那些在整個(gè)應(yīng)用中多個(gè)組件之間需要共享和訪問的狀態(tài)。與本地狀態(tài)不同,全局狀態(tài)不局限于單個(gè)組件,而是可以在應(yīng)用的不同部分之間傳遞、更新和同步。
- 重要性: 當(dāng)應(yīng)用變得越來越大,組件之間的嵌套層次越來越深時(shí),使用 props 逐層傳遞狀態(tài)(即“props drilling”)會(huì)變得非常麻煩且難以維護(hù)。全局狀態(tài)管理工具可以幫助解決這個(gè)問題,它們提供了一種集中管理和共享狀態(tài)的方式,減少了冗余代碼,并提高了開發(fā)效率。
- 使用選擇:
- 狀態(tài)提升: 當(dāng)需要在兄弟組件中共享狀態(tài)時(shí),可以將狀態(tài)提升到父組件。
- Context API:適合簡(jiǎn)單的全局狀態(tài)管理,尤其是當(dāng)需要避免 props drilling 時(shí)。
- Zustand:一個(gè)輕量級(jí)的選擇,適合小型到中型應(yīng)用,特別是那些注重性能和易用性的項(xiàng)目。
- Jotai:為更細(xì)粒度的狀態(tài)管理提供了可能性,非常適合那些尋求模塊化和高效狀態(tài)管理的開發(fā)者。
狀態(tài)提升
- 定義: 狀態(tài)提升是 React 中一種常見的模式,用于將需要在多個(gè)組件之間共享的狀態(tài)移動(dòng)到它們的最近公共父組件中進(jìn)行管理。這種方法雖不是全局狀態(tài)管理的一部分,但它提供了一種方式來集中管理跨多個(gè)兄弟組件的狀態(tài)。
- 原理: 當(dāng)兩個(gè)或更多的子組件需要訪問相同的數(shù)據(jù)時(shí),可以將該數(shù)據(jù)及其相關(guān)的更新邏輯“提升”到這些子組件的共同祖先組件中。然后,通過 props 將數(shù)據(jù)和處理函數(shù)傳遞給需要它的子組件。
- 注意事項(xiàng):
狀態(tài)更新的流向:狀態(tài)更新總是從父組件開始,父組件更新狀態(tài)后,將新的狀態(tài)作為 props 傳遞給子組件,觸發(fā)子組件的重新渲染。
避免過度提升:不要將所有狀態(tài)都盲目提升,只提升多個(gè)組件需要共享的狀態(tài),以保持組件的簡(jiǎn)潔和清晰。
Context API
- 定義: React 的 Context API 允許創(chuàng)建一個(gè)可以在組件樹中傳遞數(shù)據(jù)的上下文,使得不同層次的組件可以訪問這些數(shù)據(jù),而無需通過 props 一層層傳遞。雖然 Context API 本身并不直接提供狀態(tài)管理功能,但它可以與 Hooks(如 useState)結(jié)合使用來實(shí)現(xiàn)簡(jiǎn)單的全局狀態(tài)管理。
- 基本使用:
提供者: 在 React 19 之前,可以使用 MyContext.Provider的形式來提供共享狀態(tài)。React 19 中,可以直接使用 MyContext的形式,省略掉了.Provider。
import React, { useState, createContext } from 'react';
const MyContext = createContext();
function App() {
const [globalState, setGlobalState] = useState({ count: 0 });
return (
<MyContext.Provider value={{ globalState, setGlobalState }}>
<ComponentA />
<ComponentB />
</MyContext.Provider>
);
}- 消費(fèi)者:在子組件中可以使用 useContext 來獲取 MyContext 提供的數(shù)據(jù),實(shí)現(xiàn)對(duì)全局狀態(tài)的訪問和修改。
import React, { useContext } from 'react';
import MyContext from './MyContext';
function ComponentA() {
const { globalState, setGlobalState } = useContext(MyContext);
const increment = () => {
setGlobalState(prevState => ({...prevState, count: prevState.count + 1 }));
};
return (
<div>
<p>Count: {globalState.count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}Zustand
- 定義: Zustand 是一個(gè)輕量級(jí)且易于使用的狀態(tài)管理庫,專門為 React 應(yīng)用設(shè)計(jì)。它的名字來源于德語單詞 "zustand",意為“狀態(tài)”。Zustand 的設(shè)計(jì)理念是提供一種簡(jiǎn)單、直觀的方式來管理全局狀態(tài),同時(shí)保持高性能和小體積。
- 特點(diǎn):
簡(jiǎn)潔性:Zustand 的設(shè)計(jì)理念是保持極簡(jiǎn)主義,通過簡(jiǎn)單的 API 和最小化的配置來實(shí)現(xiàn)高效的狀態(tài)管理。
基于 Hooks:它完全依賴于 React 的 Hooks 機(jī)制,允許開發(fā)者以聲明式的方式訂閱狀態(tài)變化并觸發(fā)更新。
無特定立場(chǎng):Zustand 不強(qiáng)制任何特定的設(shè)計(jì)模式或結(jié)構(gòu),給予開發(fā)者最大的靈活性。
單一數(shù)據(jù)源:盡管 Zustand 支持多個(gè)獨(dú)立的 store,但每個(gè) store 內(nèi)部仍然遵循單一數(shù)據(jù)源的原則,即所有狀態(tài)都集中存儲(chǔ)在一個(gè)地方。
模塊化狀態(tài)切片:狀態(tài)可以被分割成不同的切片(slices),每個(gè)切片負(fù)責(zé)一部分應(yīng)用邏輯,便于管理和維護(hù)。
異步支持:Zustand 可以輕松處理異步操作,允許在 store 中定義異步函數(shù)來執(zhí)行如 API 請(qǐng)求等任務(wù)。
- 基本使用:
- 創(chuàng)建 Store:在 Zustand 中,Store是通過create函數(shù)創(chuàng)建的。每個(gè)Store都包含狀態(tài)和處理狀態(tài)的函數(shù)。
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0, // 初始狀態(tài)
increment: () => set((state) => ({ count: state.count + 1 })), // 增加count的函數(shù)
decrement: () => set((state) => ({ count: state.count - 1 })), // 減少count的函數(shù)
}));其中, create函數(shù)接受一個(gè)回調(diào)函數(shù),該回調(diào)函數(shù)接受一個(gè)set函數(shù)作為參數(shù),用于更新狀態(tài)。在這個(gè)回調(diào)函數(shù)中,定義了一個(gè)count狀態(tài)和兩個(gè)更新函數(shù)increment和decrement。
2. 使用 Store:在組件中,可以使用自定義的 Hooks(上面的useStore)來獲取狀態(tài)和更新函數(shù),并在組件中使用它們。
import React from 'react';
import { useStore } from './store';
function Counter() {
const { count, increment, decrement } = useStore();
return (
<div>
<p>Count: {count}</p>
<button notallow={increment}>Increment</button>
<button notallow={decrement}>Decrement</button>
</div>
);
}3. 訂閱特定狀態(tài)片段:如果有一個(gè)包含多個(gè)狀態(tài)的store,但在組件中只需要訂閱其中一個(gè)狀態(tài),可以通過解構(gòu)賦值從useStore返回的完整狀態(tài)對(duì)象中提取需要的狀態(tài)。Zustand的智能選擇器功能允許這樣做,而不會(huì)導(dǎo)致不必要的重新渲染。
// store.js
import { create } from 'zustand';
const useStore = create((set) => ({
count: 0,
name: 'Zustand Store',
increment: () => set((state) => ({ count: state.count + 1 })),
setName: (newName) => set({ name: newName }),
}));
export default useStore;在組件中,如果只想訂閱count狀態(tài),可以這樣做:
// MyComponent.js
import React from 'react';
import useStore from './store';
function MyComponent() {
const { count } = useStore((state) => ({ count: state.count }));
return (
<div>
<p>Count: {count}</p>
</div>
);
}
export default MyComponent;Jotai
- 定義: Jotai 是一個(gè)輕量級(jí)且靈活的 React 狀態(tài)管理庫,采用原子化狀態(tài)管理模型。它受到了 Recoil 的啟發(fā),旨在提供一種簡(jiǎn)單而直觀的方式來管理 React 中的狀態(tài)。
- 核心思想:
- 原子化狀態(tài)管理:Jotai 使用原子作為狀態(tài)的基本單位。每個(gè)原子代表一個(gè)獨(dú)立的狀態(tài)片段,可以被多個(gè)組件共享和訪問。
- 組合性:通過組合 atoms 和選擇器,可以構(gòu)建復(fù)雜的、依賴于其他狀態(tài)的狀態(tài)邏輯。這種組合性使得狀態(tài)管理更加模塊化和靈活。
- 細(xì)粒度依賴跟蹤:Jotai 內(nèi)置了高效的依賴跟蹤機(jī)制,只有當(dāng)組件實(shí)際依賴的狀態(tài)發(fā)生變化時(shí)才會(huì)觸發(fā)重新渲染。
- 基本使用:
- 簡(jiǎn)單原子創(chuàng)建:定義一個(gè) atom 來表示應(yīng)用中的某個(gè)狀態(tài)片段。
- 創(chuàng)建 Atom:
import { atom } from 'jotai';
export const countAtom = atom(0);派生原子創(chuàng)建(基于已有原子進(jìn)行計(jì)算等)
import { atom } from 'jotai';
import { countAtom } from './atoms';
export const doubleCountAtom = atom((get) => get(countAtom) * 2);- 使用 Atom:
- 使用 useAtom Hook 獲取和更新原子狀態(tài)(適用于讀寫原子狀態(tài)場(chǎng)景):
import React from 'react';
import { useAtom } from 'jotai';
import { countAtom } from './atoms';
const Counter = () => {
const [count, setCount] = useAtom(countAtom);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount((prev) => prev + 1)}>Increment</button>
<button onClick={() => setCount((prev) => prev - 1)}>Decrement</button>
</div>
);
};
export default Counter;- 使用 useAtomValue Hook 僅獲取原子狀態(tài)(適用于只讀場(chǎng)景):
import React from 'react';
import { useAtomValue } from 'jotai';
import { doubleCountAtom } from './derivedAtoms';
const DoubleCounter = () => {
const doubleCount = useAtomValue(doubleCountAtom);
return <p>Double Count: {doubleCount}</p>;
};
export default DoubleCounter;- 使用 useSetAtom Hook 僅獲取更新原子狀態(tài)的函數(shù)(適用于只寫場(chǎng)景):
import React from 'react';
import { useAtomValue } from 'jotai';
import { countAtom } from './derivedAtoms';
const IncrementButtonComponent = () => {
const setCount = useSetAtom(countAtom);
return (
<button onClick={() => setCount((prevCount) => prevCount + 1)}>Increment</button>
);
};服務(wù)器狀態(tài)
- 定義: 服務(wù)器狀態(tài)指的是應(yīng)用與服務(wù)端交互相關(guān)的狀態(tài),包括從服務(wù)器獲取的數(shù)據(jù)(如 API 響應(yīng))以及請(qǐng)求的狀態(tài)(如加載中、完成、失敗等)。
- 重要性:
數(shù)據(jù)一致性:確保前端展示的數(shù)據(jù)是最新的,并且與服務(wù)器上的數(shù)據(jù)一致。
用戶體驗(yàn):提供即時(shí)反饋,比如加載指示器、錯(cuò)誤消息等,以改善用戶的交互體驗(yàn)。
緩存策略:合理地使用緩存可以減少不必要的網(wǎng)絡(luò)請(qǐng)求,提高性能并節(jié)省帶寬。
錯(cuò)誤處理:優(yōu)雅地處理網(wǎng)絡(luò)故障或其他異常情況,保證應(yīng)用的穩(wěn)定性和可靠性。
- 使用選擇:
- useState + useEffect:當(dāng)只需要從服務(wù)器加載一次數(shù)據(jù),并且不需要復(fù)雜的緩存或重試機(jī)制時(shí)使用。
- 自定義 Hooks: 當(dāng)多個(gè)組件中有相似的數(shù)據(jù)獲取模式,可以將這部分邏輯提取成一個(gè)自定義 Hook 來減少重復(fù)代碼。
- React Query:一個(gè)強(qiáng)大的工具,特別適用于需要全面數(shù)據(jù)獲取功能的大型應(yīng)用。
- SWR:以其簡(jiǎn)潔性和對(duì)實(shí)時(shí)性的支持而聞名,是那些追求快速集成和良好用戶體驗(yàn)的應(yīng)用的理想選擇。
useState + useEffect
在 React 中,管理服務(wù)器狀態(tài)的最常見模式是結(jié)合使用useState和useEffect Hook。
- useState:用于定義組件內(nèi)的狀態(tài)變量。這些狀態(tài)可以保存從服務(wù)器獲取的數(shù)據(jù)、加載標(biāo)志(例如isLoading),以及可能發(fā)生的錯(cuò)誤信息。
- useEffect:用來處理副作用,比如發(fā)起網(wǎng)絡(luò)請(qǐng)求。它可以在組件掛載或狀態(tài)變化時(shí)執(zhí)行特定的操作。在網(wǎng)絡(luò)請(qǐng)求開始前,將isLoading設(shè)為true,表明正在加載數(shù)據(jù)。當(dāng)接收到服務(wù)器響應(yīng)后,依據(jù)響應(yīng)內(nèi)容更新相關(guān)狀態(tài)變量,并將isLoading設(shè)為false。如果請(qǐng)求過程中發(fā)生錯(cuò)誤,還可以設(shè)置一個(gè)錯(cuò)誤狀態(tài)變量來存儲(chǔ)錯(cuò)誤信息。
import React, { useState, useEffect } from 'react';
function APP() {
// data保存服務(wù)器返回的數(shù)據(jù),loading表示是否正在加載,error保存可能發(fā)生的錯(cuò)誤信息。
const [data, setData] = useState(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState(null);
const fetchData = async () => {
try {
const response = await fetch('https://api.example.com/data');
if (!response.ok) {
throw new Error('Network response was not ok');
}
const result = await response.json();
setData(result); // 更新數(shù)據(jù)狀態(tài)
} catch (err) {
setError(err.message); // 設(shè)置錯(cuò)誤信息
} finally {
setLoading(false); // 請(qǐng)求完成后關(guān)閉加載狀態(tài)
}
}
useEffect(() => {
fetchData();
}, []);
if (loading) return <p>Loading...</p>;
if (error) return <p>Error: {error}</p>;
return (
<ul>
{data.map(item => (
<li key={item.id}>{item.name}</li>
))}
</ul>
</div>
);
}
export default DataFetcher;這種方式存在問題:
- 代碼冗余: 在多個(gè)組件中重復(fù)編寫用于獲取不同服務(wù)器數(shù)據(jù)的 useState 和 useEffect 代碼,不僅增加了開發(fā)成本,還使得代碼結(jié)構(gòu)變得復(fù)雜,難以維護(hù)和擴(kuò)展。這種冗余可以通過創(chuàng)建自定義 Hook 或采用更高級(jí)的狀態(tài)管理解決方案來有效緩解。
- 狀態(tài)一致性: 當(dāng)多個(gè)組件依賴于相同的服務(wù)器數(shù)據(jù)時(shí),很難保證它們之間的狀態(tài)一致性。例如,如果一個(gè)組件更新了數(shù)據(jù),另一個(gè)組件可能不會(huì)立即感知到,需要手動(dòng)實(shí)現(xiàn)狀態(tài)同步的邏輯,可能會(huì)導(dǎo)致不同組件顯示的數(shù)據(jù)不一致。
- 缺乏高級(jí)特性: 基礎(chǔ)的狀態(tài)管理方式缺乏一些高級(jí)特性,如自動(dòng)緩存、自動(dòng)數(shù)據(jù)重新獲取以及樂觀更新等。對(duì)于復(fù)雜的數(shù)據(jù)更新場(chǎng)景,如部分更新或失效數(shù)據(jù)的重新獲取,開發(fā)者需要手動(dòng)編寫大量的額外代碼來實(shí)現(xiàn)這些功能。
- 性能問題: 由于缺乏內(nèi)置的緩存機(jī)制,對(duì)于頻繁請(qǐng)求的數(shù)據(jù),每次組件重新渲染時(shí)都可能觸發(fā)新的請(qǐng)求,從而增加性能開銷。此外,依賴相同數(shù)據(jù)的多個(gè)組件無法共享數(shù)據(jù)緩存,導(dǎo)致網(wǎng)絡(luò)資源的浪費(fèi)和不必要的請(qǐng)求開銷。
- 復(fù)雜的錯(cuò)誤處理:在處理不同類型的錯(cuò)誤(如網(wǎng)絡(luò)錯(cuò)誤、服務(wù)器端錯(cuò)誤、權(quán)限錯(cuò)誤等)時(shí),開發(fā)者需要進(jìn)行手動(dòng)區(qū)分和處理,這增加了錯(cuò)誤處理的復(fù)雜性。同時(shí),實(shí)現(xiàn)自動(dòng)重試機(jī)制或提供用戶友好的錯(cuò)誤提示也需要額外的復(fù)雜邏輯和代碼編寫。
自定義 Hooks
為了優(yōu)化 useState 和 useEffect 組合使用時(shí)所遇到的問題,可以使用自定義 Hook 來封裝常見的服務(wù)器請(qǐng)求操作。這里將以 ahooks 提供的的 useRequest 為例。
aHooks 是一個(gè)由螞蟻金服開發(fā)的 React Hooks 庫,它提供了一系列實(shí)用的自定義Hooks來簡(jiǎn)化常見的開發(fā)任務(wù)。
useRequest 的特性如下:
- 自動(dòng)請(qǐng)求/手動(dòng)請(qǐng)求
- SWR(stale-while-revalidate)
- 緩存/預(yù)加載
- 屏幕聚焦重新請(qǐng)求
- 輪詢
- 防抖
- 節(jié)流
- 并行請(qǐng)求
- 依賴請(qǐng)求
- loading delay
- 分頁
- 加載更多,數(shù)據(jù)恢復(fù) + 滾動(dòng)位置恢復(fù)
import { useRequest } from 'ahooks';
import React from 'react';
import Mock from 'mockjs';
function getUsername(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve(Mock.mock('@name'));
}, 1000);
});
}
export default () => {
const { data, loading, run, cancel } = useRequest(getUsername, {
pollingInterval: 1000,
pollingWhenHidden: false,
});
return (
<>
<p>Username: {loading ? 'loading' : data}</p>
<button type="button" onClick={run}>
start
</button>
<button type="button" onClick={cancel} style={{ marginLeft: 8 }}>
stop
</button>
</>
);
};當(dāng)然,我們也可以根據(jù)需求來完全自定義 Hooks。
React Query
React Query(現(xiàn)稱為 TanStack Query)是一個(gè)專為React應(yīng)用設(shè)計(jì)的數(shù)據(jù)獲取、緩存和狀態(tài)管理庫。它通過簡(jiǎn)化常見的數(shù)據(jù)操作任務(wù),如發(fā)起HTTP請(qǐng)求、處理加載狀態(tài)、錯(cuò)誤處理等,極大地提升了開發(fā)效率和用戶體驗(yàn)。
圖片
React Query 的功能如下:
- 簡(jiǎn)化數(shù)據(jù)獲取:可以使用 useQuery Hook 可以輕松發(fā)起網(wǎng)絡(luò)請(qǐng)求,自動(dòng)處理加載狀態(tài)、錯(cuò)誤處理,并返回響應(yīng)數(shù)據(jù)。支持多種類型的請(qǐng)求,包括RESTful API 和 GraphQL。
- 內(nèi)置緩存機(jī)制:自動(dòng)管理和優(yōu)化緩存,減少不必要的網(wǎng)絡(luò)請(qǐng)求。提供 cacheTime 和 staleTime 配置項(xiàng)來控制緩存的有效期和數(shù)據(jù)的新鮮度。
- 自動(dòng)重新獲取數(shù)據(jù):支持在特定情況下自動(dòng)刷新數(shù)據(jù),如窗口重新聚焦 (refetchOnWindowFocus) 或者網(wǎng)絡(luò)連接恢復(fù) (refetchOnReconnect)。有助于確保用戶始終看到最新的數(shù)據(jù),特別是在長(zhǎng)時(shí)間離開頁面后再返回時(shí)。
- 并發(fā)模式支持:確保在網(wǎng)絡(luò)請(qǐng)求未完成時(shí)安全地卸載組件,避免內(nèi)存泄漏和其他潛在問題。
- 樂觀更新與突變管理:通過 useMutation Hook 支持創(chuàng)建、更新或刪除資源的操作。實(shí)現(xiàn)樂觀更新,即先顯示預(yù)期的結(jié)果,如果請(qǐng)求失敗則回滾到原始狀態(tài)。
- 無限滾動(dòng)與分頁:使用 useInfiniteQuery Hook 來實(shí)現(xiàn)無限滾動(dòng)功能,簡(jiǎn)化分頁邏輯的管理。
- 開發(fā)工具:提供 ReactQueryDevtools 開發(fā)工具,幫助開發(fā)者清晰地觀察每個(gè)請(qǐng)求的狀態(tài)及其相關(guān)緩存信息。
- 手動(dòng)觸發(fā)請(qǐng)求:允許通過 manual 選項(xiàng)將請(qǐng)求設(shè)置為手動(dòng)觸發(fā),配合 run 方法按需發(fā)起請(qǐng)求。
- 全局配置:可以通過 QueryClient 進(jìn)行全局配置,如設(shè)置默認(rèn)的緩存時(shí)間、重試策略等。
import {
useQuery,
useMutation,
useQueryClient,
QueryClient,
QueryClientProvider,
} from '@tanstack/react-query'
import { getTodos, postTodo } from '../my-api'
const queryClient = new QueryClient()
function App() {
return (
<QueryClientProvider client={queryClient}>
<Todos />
</QueryClientProvider>
)
}
function Todos() {
const queryClient = useQueryClient()
const query = useQuery({ queryKey: ['todos'], queryFn: getTodos })
const mutation = useMutation({
mutationFn: postTodo,
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ['todos'] })
},
})
return (
<div>
<ul>{query.data?.map((todo) => <li key={todo.id}>{todo.title}</li>)}</ul>
<button
onClick={() => {
mutation.mutate({
id: Date.now(),
title: 'Do Laundry',
})
}}
>
Add Todo
</button>
</div>
)
}
render(<App />, document.getElementById('root'))SWR
SWR 是 "stale-while-revalidate" 的縮寫,它是一個(gè)用于數(shù)據(jù)獲取和緩存的React Hooks庫,由 Vercel 開發(fā)。SWR 專為構(gòu)建快速響應(yīng)的用戶界面而設(shè)計(jì),它的工作原理是先返回緩存的數(shù)據(jù),然后在后臺(tái)發(fā)起請(qǐng)求獲取最新的數(shù)據(jù),并在收到新數(shù)據(jù)后更新UI。這種模式可以提供即時(shí)的用戶體驗(yàn),同時(shí)確保數(shù)據(jù)保持最新。

SWR 的特性如下:
- 即時(shí)響應(yīng):當(dāng)組件首次渲染時(shí),SWR 會(huì)立即返回緩存中的舊數(shù)據(jù)(如果有)。這使得用戶界面能夠瞬間加載,無需等待網(wǎng)絡(luò)請(qǐng)求完成。
- 自動(dòng)重驗(yàn)證:一旦從緩存中讀取了數(shù)據(jù),SWR 就會(huì)在后臺(tái)發(fā)起請(qǐng)求以獲取最新數(shù)據(jù)。這個(gè)過程對(duì)用戶來說是透明的,只有當(dāng)新數(shù)據(jù)到達(dá)時(shí)才會(huì)更新UI。
- 簡(jiǎn)單的API:SWR 提供了一個(gè)非常直觀的 useSWR Hook 來簡(jiǎn)化數(shù)據(jù)獲取邏輯,包括處理加載狀態(tài)、錯(cuò)誤和成功情況。
- 支持多種數(shù)據(jù)源:雖然 SWR 最常用于HTTP請(qǐng)求,但它也可以與其他類型的數(shù)據(jù)源一起工作,比如 WebSocket 或者本地存儲(chǔ)。
- 優(yōu)化性能:SWR 內(nèi)置了一些性能優(yōu)化特性,如并發(fā)模式支持、自動(dòng)垃圾回收和去抖動(dòng)/節(jié)流功能,幫助減少不必要的請(qǐng)求。
- 開發(fā)工具集成:與 React Query 類似,SWR 也提供了開發(fā)者工具來監(jiān)控和調(diào)試數(shù)據(jù)獲取行為。
- 靈活配置:可以通過配置選項(xiàng)自定義刷新策略、重試機(jī)制等,以適應(yīng)不同的應(yīng)用場(chǎng)景需求。
- 服務(wù)端渲染(SSR)兼容:SWR 支持服務(wù)器端渲染,可以在頁面初次加載時(shí)預(yù)先填充緩存,從而加快客戶端的首次渲染速度。
import React, { useState } from 'react';
import useSWR from 'swr';
import axios from 'axios';
// 自定義的 fetcher 函數(shù),使用 axios
const fetcher = (url) => axios.get(url).then((res) => res.data);
// 自定義的 SWR 配置選項(xiàng)
const swrConfig = {
// 重試次數(shù)
retry: 3,
// 緩存時(shí)間(毫秒)
revalidateOnFocus: false,
shouldRetryOnError: false,
};
function BlogPosts() {
const [page, setPage] = useState(1);
const perPage = 10;
// 構(gòu)建 API URL,包含分頁參數(shù)
const apiUrl = `https://api.example.com/posts?page=${page}&perPage=${perPage}`;
// 使用 SWR 獲取文章數(shù)據(jù)
const { data: posts, error, isValidating } = useSWR(apiUrl, fetcher, swrConfig);
// 數(shù)據(jù)轉(zhuǎn)換:按日期排序
const sortedPosts = posts?.sort((a, b) => new Date(b.date) - new Date(a.date)) || [];
if (error) return <div>加載出錯(cuò): {error.message}</div>;
if (!posts) return <div>正在加載... {isValidating && '重新驗(yàn)證...'}</div>;
return (
<div>
<h1>博客文章</h1>
<ul>
{sortedPosts.map((post) => (
<li key={post.id}>
<h2>{post.title}</h2>
<p>{post.content}</p>
<small>發(fā)布于: {new Date(post.date).toLocaleDateString()}</small>
</li>
))}
</ul>
<button onClick={() => setPage(page - 1)} disabled={page === 1}>
上一頁
</button>
<button onClick={() => setPage(page + 1)}>
下一頁
</button>
</div>
);
}
export default BlogPosts;導(dǎo)航狀態(tài)
- 定義: React 導(dǎo)航狀態(tài)是指與應(yīng)用內(nèi)部頁面或視圖之間的導(dǎo)航相關(guān)的狀態(tài)。它包括但不限于當(dāng)前路由信息、歷史記錄棧、參數(shù)傳遞以及可能的其他元數(shù)據(jù)。當(dāng)進(jìn)行路由導(dǎo)航時(shí),有時(shí)需要將前一個(gè)頁面的狀態(tài)帶到新頁面,以確保用戶體驗(yàn)的一致性和連續(xù)性。通常,在 React 項(xiàng)目中會(huì)借助 React Router、TanStack Router 等路由庫來實(shí)現(xiàn)狀態(tài)管理。
React Router
- 定義: React Router 是 React 應(yīng)用中最常用的路由庫,提供了豐富的路由功能,支持路由匹配、嵌套路由、路由參數(shù)提取、動(dòng)態(tài)路由、重定向等??梢苑奖愕嘏c React 應(yīng)用集成,實(shí)現(xiàn)頁面之間的導(dǎo)航和導(dǎo)航狀態(tài)的管理。
- 使用:在 React Router 中,可以通過路徑參數(shù)、查詢參數(shù)、狀態(tài)對(duì)象來實(shí)現(xiàn)狀態(tài)管理。
Link:創(chuàng)建一個(gè)導(dǎo)航鏈接,to="/product/123" 表示點(diǎn)擊該鏈接會(huì)跳轉(zhuǎn)到 /product/123 路徑。
Route:定義路由規(guī)則,path="/product/:productId" 是一個(gè)包含路徑參數(shù)的路由,productId 是參數(shù)名稱。
useParams:在 ProductDetail 組件中,通過 useParams 鉤子函數(shù)可以獲取到當(dāng)前匹配路由的路徑參數(shù)。在這個(gè)例子中,它將從 URL 中提取 productId 并顯示在頁面上。
- 路徑參數(shù): 路徑參數(shù)通常用于在 URL 路徑中傳遞信息。
// 定義路由
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import UserDetail from './UserDetail';
function App() {
return (
<Router>
<Routes>
<Route path="/users/:userId" element={<UserDetail />} />
</Routes>
</Router>
);
}
export default App;// 路由跳轉(zhuǎn)
<Link to="/users/123">用戶中心</Link>// 使用參數(shù)
import { useParams } from 'react-router-dom';
function UserDetail() {
const { userId } = useParams();
return (
<div>
<h1>User Detail</h1>
<p>User ID: {userId}</p>
</div>
);
}
export default UserDetail;- 查詢字符串:查詢字符串通常用于在 URL 查詢字符串中傳遞信息。
- useLocation:通過 useLocation 獲取當(dāng)前頁面的位置信息,包括查詢參數(shù)。
- new URLSearchParams(location.search):將 location.search 部分(以 ? 開始的查詢字符串)解析為一個(gè) URLSearchParams 對(duì)象。
- queryParams.get('keyword') 和 queryParams.get('category'):通過 get 方法從 URLSearchParams 對(duì)象中獲取相應(yīng)的查詢參數(shù)。
// 定義路由
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
import SearchResults from './SearchResults';
function App() {
return (
<Router>
<Routes>
<Route path="/search" element={<SearchResults />} />
</Routes>
</Router>
);
}
export default App;// 路由跳轉(zhuǎn)
<Link to="/search?keyword=phone">搜索</Link>// 使用參數(shù)
import { useLocation } from 'react-router-dom';
function SearchResults() {
const location = useLocation();
const queryParams = new URLSearchParams(location.search);
return (
<div>
<h1>搜索結(jié)果</h1>
<p>關(guān)鍵詞: {queryParams.get('keyword') || 'None'}</p>
</div>
);
}
export default SearchResults;- 狀態(tài)傳遞:狀態(tài)對(duì)象通常用于在導(dǎo)航時(shí)傳遞復(fù)雜的狀態(tài)信息。
- useNavigate 支持傳遞額外的狀態(tài)對(duì)象給目標(biāo)頁面。這些狀態(tài)不會(huì)出現(xiàn)在 URL 中,但在目標(biāo)頁面可以通過 useLocation Hook 獲取。
// 源頁面?zhèn)鬟f狀態(tài)
import { useNavigate } from 'react-router-dom';
function ProductList() {
const navigate = useNavigate();
const handleAddToCart = (productId) => {
navigate('/cart', {
state: { productId }
});
};
return (
<div>
<h1>商品列表</h1>
<button onClick={() => handleAddToCart('123')}>添加到購物車</button>
</div>
);
}
export default ProductList;// 新頁面使用狀態(tài)
import { useLocation } from 'react-router-dom';
function Cart() {
const location = useLocation();
const { state } = location;
return (
<div>
<h1>購物車</h1>
{state ? (
<p>商品ID: {state.productId}</p>
) : (
<p>購物車為空</p>
)}
</div>
);
}
export default Cart;TanStack Router
- 定義: Tanstack Router 是由 TanStack 團(tuán)隊(duì)開發(fā)的一個(gè)用于 React 和其他框架的高性能路由庫。Tanstack Router 強(qiáng)調(diào)性能、可擴(kuò)展性和易用性,支持多種渲染環(huán)境,包括客戶端、服務(wù)端和靜態(tài)站點(diǎn)生成。
- 特點(diǎn):
動(dòng)態(tài)路由匹配:支持復(fù)雜的路徑模式,例如參數(shù)化路徑、通配符等。
嵌套路由:可以定義父子關(guān)系的路由結(jié)構(gòu),適用于構(gòu)建復(fù)雜的應(yīng)用布局。
編程式導(dǎo)航:提供了類似 useNavigate 的 Hook 來執(zhí)行編程式導(dǎo)航。
狀態(tài)管理:內(nèi)置對(duì) URL 狀態(tài)的支持,方便地將查詢參數(shù)和路徑參數(shù)傳遞給組件。
跨框架兼容:不僅限于 React,還支持 Vue、Solid 等其他前端框架。
性能優(yōu)化:通過懶加載(Lazy Loading)、代碼分割(Code Splitting)等技術(shù)來提高應(yīng)用性能。
全面的功能集:
插件系統(tǒng):擁有豐富的插件生態(tài),可以輕松添加額外的功能,如身份驗(yàn)證、緩存控制等。
服務(wù)端渲染(SSR)和靜態(tài)站點(diǎn)生成(SSG)支持:確保應(yīng)用在不同渲染環(huán)境中都能良好運(yùn)行。
類型安全:對(duì)于 TypeScript 用戶來說,Tanstack Router 提供了良好的類型定義和支持。
- 使用:
- 路徑參數(shù): 路徑參數(shù)是 URL 中固定位置的部分,用來標(biāo)識(shí)特定資源或視圖。例如,/products/:id。在 TanStack Router 中,可以使用 useParams Hook 來獲取路徑參數(shù)。
// 定義路由
import { createRouter } from '@tanstack/react-router';
const router = createRouter({
routes: [
{
path: '/products/:id',
element: () => import('./pages/ProductDetail'),
},
],
});
export default router;// 獲取路徑參數(shù)
import React from 'react';
import { useParams } from '@tanstack/react-router';
function ProductDetail() {
const params = useParams();
return <div>{params.id}</div>;
}
export default ProductDetail;查詢字符串:查詢字符串是以問號(hào) (?) 開始并由與號(hào) (&) 分隔的一系列鍵值對(duì),通常用于攜帶非層次結(jié)構(gòu)的數(shù)據(jù),如搜索關(guān)鍵詞、排序選項(xiàng)、過濾條件等。在 TanStack Router 中,可以使用 useSearchParams Hook 來訪問和修改查詢字符串。
// 路由跳轉(zhuǎn)
import { Link } from '@tanstack/react-router';
function SearchLink() {
return (
<Link to="/search?q=laptop&sort=price_asc">搜索</Link>
);
}// 獲取查詢參數(shù)
import React from 'react';
import { useSearchParams } from '@tanstack/react-router';
function SearchResults() {
const [searchParams] = useSearchParams();
const query = searchParams.get('q') || '';
const sort = searchParams.get('sort') || 'desc';
return (
<div>
<p>搜索: {query}, 分類: {sort}</p>
</div>
);
}
export default SearchResults;- URL 中,但它們可以通過編程式導(dǎo)航來傳遞,并在目標(biāo)頁面中通過 useLocation Hook 獲取。
// 傳遞狀態(tài)
import { useRouter } from '@tanstack/react-router';
function SubmitFormButton() {
const router = useRouter();
function handleSubmit() {
const formData = {
name: 'John Doe',
email: 'john.doe@example.com',
};
// Navigate to the next page and pass state
router.navigate('/confirmation', { state: formData });
}
return <button onClick={handleSubmit}>Submit Form</button>;
}// 獲取狀態(tài)
import React from 'react';
import { useLocation } from '@tanstack/react-router';
function ConfirmationPage() {
const location = useLocation();
const { state } = location;
const { name, email } = state || {};
return (
<div>
<p>Name: {name}</p>
<p>Email: {email}</p>
</div>
);
}
export default ConfirmationPage;表單狀態(tài)
- 定義: 在 React 中,表單狀態(tài)指的是用于保存和管理表單中各個(gè)輸入元素(如文本框、選擇框、復(fù)選框、單選按鈕等)的數(shù)據(jù)。這些數(shù)據(jù)反映了用戶與表單的交互情況,并且通常需要被收集起來以便后續(xù)處理,例如提交給服務(wù)器或用于本地邏輯計(jì)算。
- 重要性:
數(shù)據(jù)收集:從用戶那里獲取必要的信息,比如用戶的聯(lián)系信息、偏好設(shè)置或者訂單詳情。
驗(yàn)證邏輯:確保用戶輸入的數(shù)據(jù)符合預(yù)期格式和規(guī)則,防止無效或惡意數(shù)據(jù)進(jìn)入系統(tǒng)。
用戶體驗(yàn):提供即時(shí)反饋,比如顯示錯(cuò)誤消息、動(dòng)態(tài)更新選項(xiàng)或其他增強(qiáng)功能。
持久化:即使頁面刷新,也能夠保持未完成的表單內(nèi)容,避免用戶重新填寫。
- 使用選擇:
- 簡(jiǎn)單表單:使用 useState,它足夠簡(jiǎn)單且無需額外依賴。
- 中等復(fù)雜度表單:考慮使用 React Hook Form,特別是重視性能和希望保持依賴樹輕量化的時(shí)候。
- 復(fù)雜表單:選擇 Formik,它提供了最全面的功能集,非常適合構(gòu)建大型、復(fù)雜的表單應(yīng)用。
useState
這是最常用的方式,其中表單元素的值由 React 的狀態(tài)(useState)來控制。每當(dāng)用戶更改輸入時(shí),都會(huì)觸發(fā)一個(gè)事件處理器來更新狀態(tài),從而保持同步。
import React, { useState } from 'react';
function LoginForm() {
const [formData, setFormData] = useState({
username: '',
password: ''
});
const handleChange = (event) => {
const { name, value } = event.target;
setFormData(prevState => ({
...prevState,
[name]: value
}));
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('表單數(shù)據(jù)', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用戶名:</label>
<input
type="text"
name="username"
value={formData.username}
onChange={handleChange}
/>
</div>
<div>
<label>密碼:</label>
<input
type="password"
name="password"
value={formData.password}
onChange={handleChange}
/>
</div>
<button type="submit">登錄</button>
</form>
);
}
export default LoginForm;React Hook Form
- 定義: React Hook Form 是一個(gè)輕量級(jí)且高效的庫,它通過最小化不必要的渲染和優(yōu)化性能來構(gòu)建表單。它的核心理念是讓開發(fā)者直接與 HTML 表單元素互動(dòng),而不是使用受控組件的方式。
- 使用:
- useForm Hook 創(chuàng)建了一個(gè)表單實(shí)例,并返回了一些有用的屬性和方法。
- register 方法用于注冊(cè)表單字段并添加驗(yàn)證規(guī)則。
- handleSubmit 方法包裝了表單提交函數(shù),確保只有在所有驗(yàn)證都通過的情況下才會(huì)調(diào)用實(shí)際的提交邏輯。
- errors 對(duì)象包含了各個(gè)字段的驗(yàn)證錯(cuò)誤信息,可以在 UI 中展示給用戶。
import React from 'react';
import { useForm } from 'react-hook-form';
function LoginForm() {
const { register, handleSubmit, formState: { errors } } = useForm();
const onSubmit = (data) => {
console.log(data);
};
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
<label>用戶名:</label>
<input
{...register('username', { required: '用戶名是必填項(xiàng)' })}
/>
{errors.username && <p>{errors.username.message}</p>}
</div>
<div>
<label>密碼:</label>
<input
type="password"
{...register('password', { required: '密碼是必填項(xiàng)' })}
/>
{errors.password && <p>{errors.password.message}</p>}
</div>
<button type="submit">登錄</button>
</form>
);
}
export default LoginForm;Formik
- 定義: Formik 是另一個(gè)流行的表單管理庫,它提供了一套更全面的 API 來處理表單的狀態(tài)、驗(yàn)證、提交等操作。Formik 的設(shè)計(jì)目標(biāo)是讓開發(fā)者能夠快速地創(chuàng)建復(fù)雜的表單,而不需要編寫大量的樣板代碼。
- 使用:
- initialValues:設(shè)置初始表單值。
- validationSchema:定義驗(yàn)證規(guī)則(這里使用了 Yup)。
- onSubmit:當(dāng)表單成功提交時(shí)觸發(fā)的回調(diào)函數(shù)。
- Formik 組件包裹了整個(gè)表單,并接收三個(gè)主要屬性:
- Field 組件用于創(chuàng)建表單字段,它可以自動(dòng)與 Formik 的內(nèi)部狀態(tài)同步。
- ErrorMessage 組件用于顯示特定字段的驗(yàn)證錯(cuò)誤消息。
- isSubmitting 狀態(tài)可以用來禁用提交按鈕,防止重復(fù)提交。
import React from 'react';
import { Formik, Form, Field, ErrorMessage } from 'formik';
import * as Yup from 'yup';
// 使用 Yup 定義驗(yàn)證模式
const validationSchema = Yup.object().shape({
username: Yup.string()
.required('用戶名是必填項(xiàng)'),
password: Yup.string()
.required('密碼是必填項(xiàng)')
});
function LoginForm() {
return (
<Formik
initialValues={{ username: '', password: '' }} // 設(shè)置初始表單值
validationSchema={validationSchema} // 定義驗(yàn)證規(guī)則
onSubmit={(values, actions) => {
console.log(values);
// 可選地,在這里執(zhí)行異步操作,并在完成后調(diào)用 actions.setSubmitting(false)
actions.setSubmitting(false);
}}
>
{({ isSubmitting }) => ( // 獲取提交狀態(tài)
<Form>
<div>
<label>用戶名:</label>
<Field name="username" type="text" /> {/* 創(chuàng)建用戶名輸入框 */}
<ErrorMessage name="username" component="div" /> {/* 顯示用戶名的錯(cuò)誤信息 */}
</div>
<div>
<label>密碼:</label>
<Field name="password" type="password" /> {/* 創(chuàng)建密碼輸入框 */}
<ErrorMessage name="password" component="div" /> {/* 顯示密碼的錯(cuò)誤信息 */}
</div>
<button type="submit" disabled={isSubmitting}> {/* 提交按鈕,當(dāng)提交中時(shí)禁用 */}
登錄
</button>
</Form>
)}
</Formik>
);
}
export default LoginForm;持久化狀態(tài)
- 定義: 在 React 應(yīng)用中,持久化狀態(tài)是指將應(yīng)用的狀態(tài)保存到一個(gè)持久的存儲(chǔ)介質(zhì)中,以便在用戶關(guān)閉瀏覽器、刷新頁面或重新啟動(dòng)應(yīng)用后仍然能夠恢復(fù)這些狀態(tài)。持久化狀態(tài)對(duì)于提升用戶體驗(yàn)非常重要,因?yàn)樗梢员苊庥脩魜G失數(shù)據(jù),并且能夠讓應(yīng)用在不同會(huì)話之間保持一致的行為。
- 重要性:
- 數(shù)據(jù)保留:確保用戶輸入或選擇的數(shù)據(jù)不會(huì)因?yàn)轫撁嫠⑿禄驊?yīng)用重啟而丟失。
- 用戶體驗(yàn):提供更流暢和連續(xù)的用戶體驗(yàn),減少重復(fù)操作。
- 離線支持:允許應(yīng)用在沒有網(wǎng)絡(luò)連接的情況下繼續(xù)工作,并在網(wǎng)絡(luò)恢復(fù)時(shí)同步更改。
- 使用選擇:
- Web Storage:適合簡(jiǎn)單的、短期或長(zhǎng)期的客戶端狀態(tài)保存,尤其是用戶偏好設(shè)置。
- Cookies:主要用于處理認(rèn)證和跨頁面通信,但要注意安全性和數(shù)據(jù)量限制。
- IndexedDB:一個(gè)強(qiáng)大的客戶端數(shù)據(jù)庫解決方案,適用于需要存儲(chǔ)大量數(shù)據(jù)或結(jié)構(gòu)化數(shù)據(jù)的應(yīng)用。
- Zustand 或 Redux 中間件:結(jié)合持久化插件是全局狀態(tài)管理和持久化的優(yōu)秀選擇,特別適合大型應(yīng)用和復(fù)雜的狀態(tài)邏輯。
Web Storage
- localStorage:用于長(zhǎng)期存儲(chǔ)數(shù)據(jù),即使瀏覽器關(guān)閉或設(shè)備重啟后仍然存在。適合保存用戶偏好設(shè)置、主題選項(xiàng)等不需要立即過期的信息。
- sessionStorage:僅在當(dāng)前會(huì)話期間有效,當(dāng)頁面關(guān)閉或?yàn)g覽器標(biāo)簽頁被關(guān)閉時(shí)數(shù)據(jù)會(huì)被清除。適合臨時(shí)性的狀態(tài),如表單輸入。
import React, { useState, useEffect } from 'react';
function PersistentForm() {
const [formData, setFormData] = useState(() => {
// 初始化狀態(tài)時(shí)從 sessionStorage 中讀取數(shù)據(jù)
return JSON.parse(sessionStorage.getItem('formData')) || {};
});
useEffect(() => {
// 每次狀態(tài)變化時(shí)更新 sessionStorage
sessionStorage.setItem('formData', JSON.stringify(formData));
}, [formData]);
const handleChange = (event) => {
const { name, value } = event.target;
setFormData((prevData) => ({
...prevData,
[name]: value,
}));
};
const handleSubmit = (event) => {
event.preventDefault();
console.log('提交表單:', formData);
};
return (
<form onSubmit={handleSubmit}>
<div>
<label>用戶名:</label>
<input
type="text"
name="username"
value={formData.username || ''}
onChange={handleChange}
/>
</div>
<div>
<label>密碼:</label>
<input
type="password"
name="password"
value={formData.password || ''}
onChange={handleChange}
/>
</div>
<button type="submit">登錄</button>
</form>
);
}
export default PersistentForm;Cookies
雖然不常用作狀態(tài)管理工具,但 Cookies 可以用來存儲(chǔ)少量數(shù)據(jù)(通常不超過4KB),并且可以通過設(shè)置過期時(shí)間來控制數(shù)據(jù)的有效期限。需要注意的是,Cookies 會(huì)在每次 HTTP 請(qǐng)求中發(fā)送給服務(wù)器,因此不適合存儲(chǔ)大量數(shù)據(jù)或敏感信息。
IndexedDB
IndexedDB 是一種更強(qiáng)大的客戶端數(shù)據(jù)庫解決方案,適合存儲(chǔ)結(jié)構(gòu)化的鍵值對(duì)數(shù)據(jù),適用于需要存儲(chǔ)大量數(shù)據(jù)的應(yīng)用場(chǎng)景。它提供了事務(wù)處理能力,支持異步操作,并且比 localStorage 更加靈活。
狀態(tài)管理庫
如果已經(jīng)在項(xiàng)目中使用了 Redux 或 Zustand 這樣的全局狀態(tài)管理庫,那么可以通過集成相應(yīng)的持久化插件來實(shí)現(xiàn)狀態(tài)的持久化。這些插件可以自動(dòng)同步全局狀態(tài)與本地存儲(chǔ)之間的數(shù)據(jù)。
- Zustand:可以借助zustand/persist 中間件來實(shí)現(xiàn)狀態(tài)持久化。
import create from 'zustand'
import { persist } from '@zustand/middleware'
import { localStoragePersist } from 'zustand/persist'
const persistConfig = {
key: 'myStore', // 存儲(chǔ)在 localStorage 中的鍵名
storage: localStoragePersist, // 使用 localStorage 作為存儲(chǔ)介質(zhì)
}
const createStore = () =>
create(
persist(
(set, get) => ({
count: 0,
increment: () => set(state => ({ count: state.count + 1 })),
decrement: () => set(state => ({ count: state.count - 1 })),
})),
persistConfig
)
export const useStore = createStoreimport React from 'react'
import { useStore } from './store'
const App = () => {
const { count, increment, decrement } = useStore()
return (
<div>
<h1>Count: {count}</h1>
<button onClick={increment}>Increment</button>
<button onClick={decrement}>Decrement</button>
</div>
)
}
export default App- Redux: 可以借助redux-persist 庫來實(shí)現(xiàn)狀態(tài)持久化,redux-persist 是一個(gè) Redux中間件,用于將Redux的狀態(tài)持久化到本地存儲(chǔ)中,并在應(yīng)用重新加載時(shí)恢復(fù)狀態(tài)。
import { createStore } from 'redux';
import { persistStore, persistReducer } from 'redux-persist';
import storage from 'redux-persist/lib/storage'; // 使用默認(rèn)的localStorage
import rootReducer from './reducers'; // 引入根reducer
// 配置redux-persist
const persistConfig = {
key: 'root', // 保存數(shù)據(jù)時(shí)使用的鍵名
storage, // 存儲(chǔ)機(jī)制
};
// 創(chuàng)建持久化后的reducer
const persistedReducer = persistReducer(persistConfig, rootReducer);
// 創(chuàng)建Redux Store
const store = createStore(persistedReducer);
// 創(chuàng)建persistor對(duì)象,用于在應(yīng)用中集成PersistGate
const persistor = persistStore(store);
export { store, persistor };import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { PersistGate } from 'redux-persist/integration/react';
import { store, persistor } from './store'; // 引入剛才設(shè)置的store和persistor
import App from './App';
ReactDOM.render(
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<App />
</PersistGate>
</Provider>,
document.getElementById('root')
);

































