前言:
React 中用于状态管理的hook及库有:useState、useReducer、useContext、useReducer + useContext和一些第三方的库如redux、mobx等。
1、useState
单一组件某个具体状态
2、useReducer
单一组件中多个状态管理,策略分发机制统一管理
3、useContext
创建了一个全局状态机制,可分发给下面所有的children
4、useReducer + useContext
使用 React.createContext 创建一个 Context 对象。这个 Context 将用于在组件树中传递全局状态和更新状态的方法,Context 通常会有一个 Provider 组件,Provider 组件内部,使用 useReducer 来管理全局状态
5、redux库
Redux 的原理是通过单一的不可变的存储中心(store)来保存应用的状态,利用纯函数(reducer)来处理状态的更新,借助 action 来描述状态的变化意图,实现了可预测的状态管理和组件之间的数据共享。
一、useReducer
与useState
区别
由于useReducer是用在单一组件中多个状态管理,是否可以用useState代替呢?
useReducer
和useState
在某些情况下可以相互替代,但它们也有一些不同之处:
1、功能和使用场景
1.1 useReducer
通常适用于以下场景:
复杂的状态逻辑管理:
当状态的更新逻辑较为复杂,包含多个子状态之间的相互依赖关系或多个不同的操作类型时,useReducer
可以使代码更清晰、更具可读性和可维护性。
共享状态逻辑:
如果多个组件需要共享相同的状态更新逻辑,useReducer
可以将 reducer 函数提取出来,方便在不同组件中复用。
优化性能:
在某些情况下,useReducer
会比useState
更高效,特别是当状态更新频繁且计算量较大时,因为 reducer 函数通常是纯函数,React 可以更有效地进行优化。
1.2 useState
则更适合以下场景:
简单的状态管理:
当状态更新逻辑比较简单,只有一个或几个基本的状态值需要管理时,useState
是一个更直接和简洁的选择。
小型组件:
对于小型组件或不需要复杂状态管理的组件,useState
的语法更简单,代码量更少。
2、代码结构和可读性
2.1 useReducer
:
使用useReducer
时,需要定义一个 reducer 函数来处理状态的更新。这个 reducer 函数接收当前的状态和一个动作(action)作为参数,并返回新的状态。
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:
return state;
}
}
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>
);
}
这种方式将状态更新的逻辑集中在 reducer 函数
中,使得代码结构更清晰,尤其是当状态更新逻辑复杂时,更容易理解和维护。
2.2 useState
:
useState
的语法相对简单,直接返回一个状态值和一个更新状态的函数。
import { useState } from 'react';
function Counter() {
const [count, setCount] = useState(0);
return (
<div>
<p>Count: {count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
<button onClick={() => setCount(count - 1)}>-</button>
</div>
);
}
对于简单的状态更新,`useState`的代码更简洁直观,易于理解。
3、性能优化方面
3.1 useReducer
:
在某些情况下,useReducer
可以更好地进行性能优化。因为 reducer 函数是纯函数,React 可以更容易地跟踪状态的变化,并根据需要进行优化。例如,当状态更新是基于前一个状态值计算时,useReducer
可以确保每次更新都是基于最新的状态值,避免潜在的竞态条件。
如果状态更新的计算量较大,将计算逻辑放在 reducer 函数中,可以让 React 更好地理解状态的变化过程,从而进行更有效的优化。
3.2 useState
:
useState
在简单的状态更新场景下性能表现也很好。但如果在状态更新过程中存在复杂的计算或依赖关系,可能需要手动处理一些性能优化问题,比如使用useMemo
或useCallback
来避免不必要的重新渲染。
综上所述,虽然在单一组件中,对于简单的状态管理,useState
可能更方便快捷,但对于复杂的状态逻辑和需要更好的性能优化的场景,useReducer
是一个很好的选择。在实际开发中,可以根据具体的需求和项目特点来决定使用哪种方式。
二、 useReducer
+ useContext
以下是一个使用 useReducer
和 useContext
结合 TypeScript 来模拟类似 Redux 功能的代码示例:
1. 创建 types.ts
文件来定义接口和类型:
// 定义动作类型
export enum ActionTypes {
INCREMENT = "INCREMENT",
DECREMENT = "DECREMENT",
}
// 定义状态接口
export interface IState {
count: number;
}
// 定义动作接口
export interface IAction {
type: ActionTypes;
}
2. 创建 reducer.ts
文件来定义 reducer 函数:
import { IState, IAction, ActionTypes } from "./types";
const initialState: IState = {
count: 0,
};
const reducer = (state: IState = initialState, action: IAction): IState => {
switch (action.type) {
case ActionTypes.INCREMENT:
return { count: state.count + 1 };
case ActionTypes.DECREMENT:
return { count: state.count - 1 };
default:
return state;
}
};
export {reducer, initialState};
3. 创建 context.ts
文件来定义 Context 和 Provider:
import { createContext, useReducer, Dispatch } from "react";
import { IAction} from "./types";
import {reducer, initialState}from "./reducer";
// 创建 Context
export const StoreContext = createContext<{
state: IState;
dispatch: Dispatch<IAction>;
} | null>(null);
// 创建 Provider 组件
export const StoreProvider: React.FC = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState);
return (
<StoreContext.Provider value={{ state, dispatch }}>
{children}
</StoreContext.Provider>
);
};
4. 在组件中使用:
import React from "react";
import { StoreContext } from "./context";
import { useContext } from "react";
const MyComponent: React.FC = () => {
const { state, dispatch } = useContext(StoreContext)!;
const increment = () => {
dispatch({ type: ActionTypes.INCREMENT });
};
const decrement = () => {
dispatch({ type: ActionTypes.DECREMENT });
};
return (
<div>
<p>Count: {state.count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
};
export default MyComponent;
5. 在应用的入口文件中包裹:
import React from "react";
import ReactDOM from "react-dom";
import { StoreProvider } from "./context";
import MyComponent from "./MyComponent";
ReactDOM.render(
<StoreProvider>
<MyComponent />
</StoreProvider>,
document.getElementById("root")
);
在这个示例中,通过定义明确的接口和类型,使用 useReducer
和 useContext
来管理状态和提供状态给组件,模拟了类似 Redux 的状态管理模式。
三、使用 useReducer
和 useContext
实现的购物车
以下是一个使用 useReducer
和 useContext
实现的购物车案例:
1. 首先创建一个 Context
:
import React from 'react';
// 创建购物车上下文
const CartContext = React.createContext();
2. 定义 reducer
函数和初始状态:
// 初始购物车状态
const initialCartState = {
items: [],
};
// 购物车 reducer 函数
function cartReducer(state, action) {
switch (action.type) {
case 'ADD_ITEM':
// 检查是否已存在该商品,若存在则增加数量,否则添加新商品
const existingItemIndex = state.items.findIndex(
(item) => item.id === action.payload.id
);
if (existingItemIndex!== -1) {
const updatedItems = [...state.items];
updatedItems[existingItemIndex].quantity += 1;
return {...state, items: updatedItems };
} else {
return {
...state,
items: [
...state.items,
{...action.payload, quantity: 1 },
],
};
}
case 'REMOVE_ITEM':
return {
...state,
items: state.items.filter(
(item) => item.id!== action.payload
),
};
default:
return state;
}
}
3. 创建 Provider
组件:
function CartProvider({ children }) {
const [cartState, dispatch] = React.useReducer(cartReducer, initialCartState);
const addItemToCart = (item) => {
dispatch({ type: 'ADD_ITEM', payload: item });
};
const removeItemFromCart = (itemId) => {
dispatch({ type: 'REMOVE_ITEM', payload: itemId });
};
const value = {
cartState,
addItemToCart,
removeItemFromCart,
};
return (
<CartContext.Provider value={value}>
{children}
</CartContext.Provider>
);
}
4. 在组件中使用:
function Product({ product, addToCart }) {
return (
<div>
<h3>{product.name}</h3>
<p>{product.price}</p>
<button onClick={() => addToCart(product)}>添加到购物车</button>
</div>
);
}
function Cart() {
const { cartState, removeItemFromCart } = React.useContext(CartContext);
return (
<div>
<h2>购物车</h2>
{cartState.items.map((item) => (
<div key={item.id}>
<p>{item.name} - 数量: {item.quantity}</p>
<button onClick={() => removeItemFromCart(item.id)}>
从购物车中移除
</button>
</div>
))}
</div>
);
}
function App() {
return (
<CartProvider>
<Product
product={{ id: 1, name: '商品 1', price: 10 }}
addToCart={(product) => addItemToCart(product)}
/>
<Cart />
</CartProvider>
);
}
export default App;
在这个例子中:
CartContext
用于创建购物车的上下文。cartReducer
处理购物车状态的更新逻辑,包括添加商品和移除商品。CartProvider
组件使用useReducer
创建状态和dispatch
函数,并将其和操作函数通过CartContext.Provider
提供给子组件。Product
组件表示一个商品,点击按钮可以将商品添加到购物车。Cart
组件展示购物车中的商品,并提供移除商品的功能。
四、Redux
React 使用 Redux 进行状态管理的代码实例:
1. 首先安装必要的库:
npm install redux react-redux
2. 创建 Redux 的 store
:
创建一个 store.js
文件:
import { createStore } from 'redux';
// 定义初始状态
const initialState = {
count: 0
};
// 定义 reducer
const counterReducer = (state = initialState, action) => {
switch (action.type) {
case 'INCREMENT':
return { count: state.count + 1 };
case 'DECREMENT':
return { count: state.count - 1 };
default:
return state;
}
};
// 创建 store
const store = createStore(counterReducer);
export default store;
3. 创建 React 组件:
App.js
:
import React from 'react';
import { connect } from 'react-redux';
import { increment, decrement } from './actions';
const App = ({ count, increment, decrement }) => {
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>+</button>
<button onClick={decrement}>-</button>
</div>
);
};
// 从 Redux store 中获取 state 映射到组件的 props
const mapStateToProps = (state) => ({
count: state.count
});
// 将 action creators 映射到组件的 props
const mapDispatchToProps = {
increment,
decrement
};
// 使用 connect 连接组件和 Redux store
export default connect(mapStateToProps, mapDispatchToProps)(App);
4. 创建 actions.js
文件来定义 action creators
:
export const increment = () => ({
type: 'INCREMENT'
});
export const decrement = () => ({
type: 'DECREMENT'
});
5. 在 index.js
中渲染应用并连接 store
:
import React from 'react';
import ReactDOM from 'react-dom';
import App from './App';
import { Provider } from 'react-redux';
import store from './store';
ReactDOM.render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('root')
);
在这个例子中,通过 Redux 管理了一个简单的计数器状态,reducer
根据不同的 action
类型来更新状态,React
组件通过 connect
函数从 Redux store
中获取状态和 action creators
来触发状态的更新。