Skip to content

前端状态管理

需求分析

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());
}