Skip to content

状态管理

前端状态管理

需求分析

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