前端状态管理¶
需求分析¶
React 的核心逻辑是状态驱动更新。
如果一个组件功能简单,state 仅在当前组件内部使用,就是一个简单的有状态组件。
如果功能复杂,多个组件共用一个状态,需要状态提升,提升到若干个组件的公共父节点中,通过 props 传递给子组件,子组件通过回调函数形式改动父组件的 state。
如果功能更复杂,组件嵌套层级很深(实际项目中超过10层有很多),或者很多子组件都改动 state,就需要逐层传值。view 和 store 层没有明显的区分,可能组件内部实现的逻辑比较混乱。redux 解决的问题,就是把状态单独维护,避免直接更改状态。此时,可以使用 context 解决问题,也可以有更好的思路 redux。
不同的框架也派生出不同的辅助库,例如 react-redux, vuex, pinia 等等。本项目使用 react-redux 进行状态管理。
实现原理¶
传统React是直接更改 State,然后不同组件维护不同的 State,公共State维护在顶层组件中。
redux 的原理是把公共 State 维护在 redux 中,然后每一个组件 getState 获取属性,然后 dispatch(action)发出事件,统一更新状态,更新后,全部组件重新渲染,实现了 State 单独维护的目的。
代码示范¶
区分复杂状态和简单状态。如果在多个组件中使用状态,那么使用 react-redux 管理。如果是某一个组件内部是使用的状态,放在组件内部管理。
类组件¶
类组件,手动写 connect(mapStateToProps, mapDispatchToProps)(App)
reducer
import { combineReducers } from 'redux';
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 combineReducers({ numReducer });
store
import { configureStore } from '@reduxjs/toolkit';
import fileIndexReducer from './reducers/file-index-reducer';
export default configureStore({
reducer: {
fileIndex: fileIndexReducer,
},
});
根节点
import { Provider } from 'react-redux';
import store from './store';
<Provider store={store}>
<App />
</Provider>
业务组件 app.js
const mapStateToProps = (state, props) => ({ ...state, ...props });
const mapDispatchToProps = (dispatch) => (
{
addFileIndex: (payload) => dispatch({ type: NUM_ADD, payload }),
deleteFileIndex: (payload) => dispatch({ type: NUM_REDUCE, payload }),
changeFileIndex: (payload) => dispatch({ type: NUM_CHANGE, payload }),
}
);
export default connect(mapStateToProps, mapDispatchToProps)(App);
// onClick = this.props.addFileIndex(1);
函数组件¶
函数组件(Hook)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;
业务组件
import { useSelector, useDispatch } from 'react-redux';
import { increment } from '../../reducers/counter-slice-reducer';
const count = useSelector((state) => state.counter.value);
const dispatch = useDispatch();
function onClick() {
dispatch(increment());
}