Redux状态管理

245 阅读5分钟

✍️ 使用Redux进行状态管理,简单的梳理和记录一下自己对Redux的工作流的理解以及使用


📌一、store

——state为单一数据源,用来存储全局共享的数据。)

使用:

// 获取数据
store.getState().___

// 更新组件
store.subscribe( () => {......} )   // 监听更新后触发,再次获取新的state
store.unsubscribe()   // 卸载组件以解除状态监听

📌二、action

(——集中管理所有要做的行为,用来描述将要触发的事件。它只描述了一个行为)

使用:

// 一、action.js 定义
export const addConst = (payload) => {
    type: "ADD_CONST",
    payload: payload,
}
export const minusConst = (payload) => {
    type: "SUB_CONST",
    payload: payload,
}

// 二、组件中导入对应定义好的 action 使用
import { add } from 'action'

// 通过 store 的 dispatch() 分发 action
store.dispatch( add(___) )

// 三、接着进入 state ,并带当前的state和 action 进入 reducer 执行对应的函数更改状态

💡注意点:

  • 实际上完全可以不进行action环节,组件可以直接store.dispatch执行reducer,进行更新state状态,但是这样代码会比较混乱,导致异步操作和任何动作全部写在组件内,不易于追踪维护; (要写一堆杂七杂八东西在组件当中 😵)
  • 将所有要做的事情以函数形式并返回一个对象,然后再进行执行reducer,这样组件只需要调用action即可,异步操作和副作用都可以放到action当中中;
  • 在Redux原生用法中action仅仅用于返回一个对象给dispatch(......)函数参数使用;

📌三、reducer

(——通过触发的 action 事件,Store会自动来执行reducer并改变返回最新 state 值,描述如何改变数据)

// 在reducer.js 定义
const initialState = { count: 0 };
const reducer = (state = initialState, action) => {
  switch (action.type) {
    case "ADD_CONST":
      return { count: state.count + action.payload };
    case "SUB_CONST":
      return { count: state.count - action.payload };
    default:
      return state;
  }
};
export default reducer;

💡注意点:

  • reducer必须是一个纯函数,不能有任何的副作用 (同 Vuex中的 mutations);
  • reducer仅仅只进行store.state状态的直接更新,这样才能追踪且可预测到状态的更新; (才能够热重载和时间旅行)
  • 可以对reducer进行拆分,最后再并到一个rootReducer中执行组合;

📌四、与Vuex的比较

  • Redux的store.dispatch(......) 类似于Vuex中的 ⇒ 组件的this.$store.dispatch(...) + Action的context.commit(...) (其实更像commit,只不过可以通过函数返回对象参数的形式类似this.$store.dispatch的效果);
  • 在Vuex中,组件可以直接进行commit()提交一个mutations修改state, 或者通过dispatch(...)context.commit(...) 提交mutations修改state; (作用上看Vuex的actions更像redux中间件处理异步行为)
  • 在Redux中通过store.dispatch(......)触发并执行reducer函数, 不过通常大多数dispatch的参数经过由action处理并返回对象(进行所有的异步和副作用);

📌五、React-Redux

(——React官方库,增加更好的API帮助React使用Redux)

// 一、引入
import { Provider } from 'react-redux'

// 二、包裹根组件
<Provider store={store}>
    <App />
</Provider>

// 三、在组件中使用 connect 高阶组件   (接收一个对象并返回一个高阶组件)
import { connect } from "react-redux";
const ReduxDemo: React.FC = (props) => {
    return (
	...
    )
}

// 定义好需要映射的state
const mapStateToProps = (state) => {
  return {
    count: state.count,
  };
};
// 定义好需要映射的despatch
const mapDispatchToProps = (dispatch) => {
  return {
    imcrement() {
	dispatch(actionA())
    }
  };
};

export default connect(mapStateToProps)(ReduxDemo);

// 随后即可在组件 props 当中直接使用对应的 props.stateName 或者 props.actionName 使用

📌六、Redux-Thunk中间件

——可以让Redux的dispatch(......)方法执行函数,而不仅仅是对象,实现在action中执行dispatch进行异步操作,执行副作用,并把执行结果再dispatch到reducer中进行更改state状态,在组件只需要dispatch触发action即可


📌七、React-saga

——实现在组件dispatch后派发的action,会被saga拦截到,并在saga中进行异步操作后,在执行dispatch,给state的reducer更新状态;解决react-Thunk改变action的行为(action仅仅返回一个action对象,不应该执行其他操作,保持了其本质和纯度),而异步副作用等等则交给saga处理;

// 例子:
// store/index.js
import { rootSaga } from "./saga";
const sagaMiddleware = createSagaMiddleware();
export default createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(rootSaga);

// saga.js
import { takeEvery, put } from "redux-saga/effects";
import { changeAction } from "./action";
function* addFun(action) {
  // action为被拦截的action
  // 注意:拦截和saga再次派发的不能是同一个action,否则死循环
  yield put(changeAction(action.value));
}
export function* rootSaga() {
  // 监听拦截 对应dispatch的action的type
  yield takeEvery(ADD_COUNT, addFun);
  yield takeEvery(SUB_COUNT, subFun);
}

// components/myCom.js
import { addAction } from "../store/action";
// 注意:即使此被saga拦截但仍然会派发原来的action给到state的reducer处
dispatch(addAction(value))   //{ type: ADD_COUNT, value: payload }

📌八、使用dva

——轻量的React 应用级框架,集中整合React-Router + Redux + Redux-saga,其解决了store和saga过于分散,不便与开发,项目结构复杂的问题。

即:dva = React-Router + Redux + Redux-saga

Models.js

  • namespace当前 Model 的名称。整个应用的 State,由多个小的 Model 的 State 以 namespace 为 key 合成

  • State:状态数据

  • Action:改变 State 的唯一途径,普通javascript对象,需在组件props.dispatch 函数派发;

    dispatch({
      type: 'add',   // required 用于指明具体的行为
      payload: {},
      ...
    })
    
  • Reducer:把之前的state和当前的状态聚合,返回的是一个新的累积结果,聚合积累的结果是当前 model 的 state 对象;

    // Action 处理器 处理同步动作
    reducers: {
        add(state, { payload: id }) {
          return newState
        },
        delete(state, { payload: id }) {
          return newState
        },
      },
    
  • Effect:执行副作用,最常见的异步操作;Dva采用了generator的相关,所以将内部异步转成同步写法,从而将effects转为纯函数;

    // Action 处理器 处理异步动作   (注意:同redux-saga,依然会经过reducers更新store.state)
    effects: {
    // 内部是一个 Generator 函数,使用 yield 关键字,标识每一步的操作(不管是异步或同步)
      *add(action, { call, put }) {
          yield call(delay, 1000);
          yield put({ type: 'add' });
       },
       *sub() {
         ...
       }
    },
    
  • Subscription:用于提前定义好需要的订阅源数据

    subscriptions: {
      keyEvent({dispatch}) {
      key('ctrl+up', () => { dispatch({type:'add'}) });
    },
    

API:

  • dva通常将容器组件约束为 Route Components(页面级别的组件)

  • connect方法(高阶组件):绑定 State 到 View,即让容器组件可以使用state,返回一个组件;(参数同react-redux)

  • effect 函数内部常用处理函数:

    • put:派发出一个 Action,类似于 dispatch
    • call:执行异步函数,用于调用异步逻辑,支持 promise
    • select:用于从 state 里获取数据 select(state => state.todos)