状态管理
前端状态管理¶
需求分析¶
React 的核心逻辑是状态驱动更新。
如果组件功能简单,且状态只在当前组件内使用,可以将状态保存在组件内部。
如果多个组件需要共享状态,可以先将状态提升到它们的共同父组件,再通过 props 和回调函数传递数据及更新操作。
当共享状态较多、组件层级较深或多个组件都需要更新同一状态时,逐层传递 props 会增加维护成本。可以根据复杂度使用 Context,或使用 Redux 等状态管理方案集中管理共享状态。
不同的框架也派生出不同的辅助库,例如 react-redux, vuex, pinia 等等。本项目使用 react-redux 进行状态管理。
实现原理¶
在普通 React 组件中,状态由组件自身或其共同父组件维护,并通过 props 传递。
Redux 将共享状态保存在 store 中。组件通过 selector 读取所需状态,并通过 dispatch(action) 发起更新;reducer 根据 action 计算新状态,订阅相关状态的组件随之更新。组件不会直接修改 store 中的状态。
代码示范¶
区分共享状态和局部状态:多个组件共享的状态由 React Redux 管理,单个组件使用的状态保留在组件内部。
类组件¶
类组件可通过 connect(mapStateToProps, mapDispatchToProps) 连接 store。以下为简化示例。
reducer
import { NUM_ADD, NUM_REDUCE, NUM_CHANGE } from "./reducer-types";
// class component reducer
function numReducer(state = 0, action) {
switch (action.type) {
case NUM_ADD: {
return state + action.payload;
}
case NUM_REDUCE: {
return state - action.payload;
}
case NUM_CHANGE: {
return action.payload;
}
default: {
return state;
}
}
}
export default numReducer;
store
import { configureStore } from "@reduxjs/toolkit";
import numReducer from "./reducers/num-reducer";
export default configureStore({
reducer: {
num: numReducer,
},
});
根节点
import { Provider } from "react-redux";
import store from "./store";
<Provider store={store}>
<App />
</Provider>;
业务组件 app.js
import { connect } from "react-redux";
import { NUM_ADD, NUM_REDUCE, NUM_CHANGE } from "./reducer-types";
const mapStateToProps = (state) => ({ num: state.num });
const mapDispatchToProps = (dispatch) => ({
addNum: (payload) => dispatch({ type: NUM_ADD, payload }),
reduceNum: (payload) => dispatch({ type: NUM_REDUCE, payload }),
changeNum: (payload) => dispatch({ type: NUM_CHANGE, payload }),
});
export default connect(mapStateToProps, mapDispatchToProps)(App);
// onClick = this.props.addNum(1);
函数组件¶
函数组件可使用 useSelector 和 useDispatch:
reducer
import { createSlice } from "@reduxjs/toolkit";
// function component reducer
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;
将该 reducer 注册到 store 后,组件才能通过 state.counter 读取对应状态:
import { configureStore } from "@reduxjs/toolkit";
import counterReducer from "./reducers/counter-slice-reducer";
export default configureStore({
reducer: {
counter: counterReducer,
},
});
业务组件
import { useSelector, useDispatch } from "react-redux";
import { increment } from "../../reducers/counter-slice-reducer";
function Counter() {
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
function onClick() {
dispatch(increment());
}
return <button onClick={onClick}>{count}</button>;
}