let us say say Redux和它的中间件机制

1,489 阅读7分钟

Redux

首先一张图看看Redux基本内容:

image.png

react-redux

还是一张图看看如何在React中使用Redux:

image.png

combineReducers

combineReducers 是 Redux 提供的一个函数,用于将多个 reducer 合并成一个单一的 reducer。它的作用是将多个处理不同 state 切片的 reducer 函数合并成一个根 reducer,从而让 Redux store 能够管理更复杂的 state 结构。

比如我们存在多个reduxer:

// userReducer.js
const initialUserState = {
  name: '',
  age: 0,
};

const userReducer = (state = initialUserState, action) => {
  switch (action.type) {
    case 'SET_USER_NAME':
      return { ...state, name: action.payload };
    case 'SET_USER_AGE':
      return { ...state, age: action.payload };
    default:
      return state;
  }
};

export default userReducer;

// postsReducer.js
const initialPostsState = {
  posts: [],
};

const postsReducer = (state = initialPostsState, action) => {
  switch (action.type) {
    case 'ADD_POST':
      return { ...state, posts: [...state.posts, action.payload] };
    case 'REMOVE_POST':
      return { ...state, posts: state.posts.filter(post => post.id !== action.payload) };
    default:
      return state;
  }
};

export default postsReducer;

需要将多个reducer合并后发送:

// rootReducer.js
import { combineReducers } from 'redux';
import userReducer from './userReducer';
import postsReducer from './postsReducer';

const rootReducer = combineReducers({
  user: userReducer,
  posts: postsReducer,
});

export default rootReducer;

最后创建store:

// store.js
import { createStore } from 'redux';
import rootReducer from './rootReducer';

const store = createStore(rootReducer);

export default store;

中间件

这就不是一张图可以解释的了。

什么是中间件

允许开发者在派发一个action到他最终被reducer处理之前,对action进行一些额外的处理。

主要是增强store.dispatch的能力。

如何自定义中间件

重写dispatch方法

类似于VUE中数组实现响应式的原理:

import { combineReducers,createStore } from 'redux';
import countReducer from "./reducers/count";
import userReducer from "./reducers/user";

const rootReducer = combineReducers({
  count:countReducer,
  user:userReducer
});

const store = createStore(rootReducer);

// 通过重写dispatch实现响应式
const originDispatch= store.dispatch; //缓存原始dispatch
store.dispatch = function(action){
  // 执行中间操作
  console.log('中间件获取action',action);
  console.log('中间件获取state',store.getState());
  originDispatch(action); //调用原始dispatch
};

export default store;

官方推荐方法

自定义 Redux 中间件通常涉及编写一个函数,该函数接收 store 对象的 dispatchgetState 方法,并返回一个函数,该函数再次接收 nextaction,允许你在 action 到达 reducer 之前或之后执行一些逻辑。

const loggerStateMiddleware = store => next => action => {
  console.log('自定义middleWare2-logger-state', store.getState());
  return next(action);
};

export default loggerStateMiddleware;


const store = createStore(rootReducer,applyMiddleware(loggerStateMiddleware));

中间件的原理

首先从使用中间件的语法入手:

const store = createStore(
  rootReducer,
  applyMiddleware(middleware1, middleware2)
);

那么接下来看看createStore做了什么呢?简化版createStore如下:

function createStore(reducer, preloadedState, enhancer) {

/**createStore接收三个参数
 * 第一个参数是 rootReducer,用于处理状态变化,
 * 第二个参数是可选的初始状态(preloadedState),
 * 第三个参数是中间件链enhancer。
 */
 
  // 如果传入了 enhancer,则使用 enhancer 增强 createStore
  if (typeof enhancer === 'function') {
    return enhancer(createStore)(reducer, preloadedState);
  }

  // 内部状态
  let currentState = preloadedState;
  let currentReducer = reducer;
  let listeners = [];

  // 获取当前状态
  function getState() {
    return currentState;
  }

  // 触发 action,并更新状态
  function dispatch(action) {
    currentState = currentReducer(currentState, action);

    // 通知所有监听器(订阅者)
    listeners.forEach(listener => listener());

    return action;
  }

  // 订阅状态变化
  function subscribe(listener) {
    listeners.push(listener);

    // 返回一个取消订阅的函数
    return function unsubscribe() {
      listeners = listeners.filter(l => l !== listener);
    };
  }

  // 初始化 store
  dispatch({ type: '@@redux/INIT' });

  // 返回 store 对象
  return {
    getState,
    dispatch,
    subscribe,
  };
}

知道了createStore的基本处理,接下来看看applyMiddleware做了什么:

由下可以看出:

  • applyMiddleware内部也重写了dispatch
function applyMiddleware(...middlewares) {
  // 返回一个高阶函数,接收 createStore 作为参数
  return createStore => reducer => {
    // 创建原始的 Redux store
    const store = createStore(reducer);
    
    // 初始化 dispatch 函数,指向原始的 store.dispatch
    let dispatch = store.dispatch;

    // middlewareAPI 包含 getState 和 dispatch 方法
    const middlewareAPI = {
      getState: store.getState,
      dispatch: action => dispatch(action)
    };

    // 将每个中间件传入 middlewareAPI,得到包装后的中间件函数
    const chain = middlewares.map(middleware => middleware(middlewareAPI));

    // compose 函数用于将多个函数从右到左组合起来
    dispatch = compose(...chain)(store.dispatch);

    // 返回一个增强版的 store,其中的 dispatch 已经被中间件包装
    return {
      ...store,
      dispatch
    };
  };
}

// compose 函数用于将多个函数组合成一个函数
function compose(...funcs) {
  return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}

接下来一张图就能说清整体的流程了:

image.png

常用的中间件

redux-thunk

redux-thunk 是 Redux 官方推荐的中间件,用于处理简单的异步逻辑。Thunk 是一个返回函数的函数,可以在 action creator 中执行异步代码。

使用

  1. 安装 :npm install redux-thunk

  2. 注册中间件

    import { createStore, applyMiddleware } from 'redux';
    import thunk from 'redux-thunk';
    import rootReducer from './reducers';
    
    const store = createStore(rootReducer, applyMiddleware(thunk));
    
  3. 使用

    // actions.js
    export const fetchData = () => {
      return async (dispatch) => {
        dispatch({ type: 'FETCH_DATA_REQUEST' });
        try {
          const response = await fetch('https://api.example.com/data');
          const data = await response.json();
          dispatch({ type: 'FETCH_DATA_SUCCESS', payload: data });
        } catch (error) {
          dispatch({ type: 'FETCH_DATA_FAILURE', error });
        }
      };
    };
    
    
  4. 触发异步操作

    import { useDispatch } from 'react-redux';
    
    const MyComponent = () => {
      const dispatch = useDispatch();
    
      useEffect(() => {
        dispatch(fetchData());
      }, [dispatch]);
    
      // 其他组件逻辑
    };
    
    

原理

同步情况下,dispatch函数的参数是一个对象:{type:'',payload} 。在使用resux-thunk时,传入的是要执行的函数,函数接收dispatch和getState做为参数,拿到异步结果后,可以dispatch一个action对象。

他的原理就是判断每个经过它的action:如果是function类型,就调用这个function(并传入 dispatch 和 getState 及 extraArgument 为参数)

redux-promise-middleware

redux-promise-middleware 是 Redux 中间件的一种,旨在简化异步操作的处理,特别是那些基于 Promise 的异步操作。它通过将 Promise 的状态(如 pending、fulfilled、rejected)自动转换成相应的 Redux action,简化了异步数据流的管理。

安装

首先,安装 redux-promise-middleware

npm install redux-promise-middleware

基本用法

以下是一个使用 redux-promise-middleware 的示例,包括如何配置 store、定义异步 action 以及处理相应的 reducer。

1. 配置 Redux Store

在你的 Redux store 配置中添加 redux-promise-middleware

// store.js
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import rootReducer from './reducers';

const store = createStore(
  rootReducer,
  applyMiddleware(promiseMiddleware)
);

export default store;

2. 定义异步 Action Creator

定义一个返回 Promise 的 action creator:

// actions.js
export const fetchData = () => ({
  type: 'FETCH_DATA',
  payload: fetch('https://api.example.com/data').then(response => response.json())
});

redux-promise-middleware 会自动处理这个 action,分别派发 FETCH_DATA_PENDING, FETCH_DATA_FULFILLED, 和 FETCH_DATA_REJECTED action。

3. 处理相应的 Reducer

定义一个 reducer 来处理这些 action:

// dataReducer.js
const initialState = {
  items: [],
  loading: false,
  error: null,
};

const dataReducer = (state = initialState, action) => {
  switch (action.type) {
    case 'FETCH_DATA_PENDING':
      return { ...state, loading: true, error: null };
    case 'FETCH_DATA_FULFILLED':
      return { ...state, loading: false, items: action.payload };
    case 'FETCH_DATA_REJECTED':
      return { ...state, loading: false, error: action.payload };
    default:
      return state;
  }
};

export default dataReducer;

4. 在组件中调用异步 Action

使用 connect 函数从 react-redux 库将 fetchData 连接到组件的 props 中,并在需要时调用它。例如,在组件挂载时调用 fetchData

// MyComponent.js
import React, { useEffect } from 'react';
import { connect } from 'react-redux';
import { fetchData } from './actions';

const MyComponent = ({ fetchData, data, loading, error }) => {
  useEffect(() => {
    fetchData();
  }, [fetchData]);

  if (loading) {
    return <p>Loading...</p>;
  }

  if (error) {
    return <p>Error: {error.message}</p>;
  }

  return (
    <div>
      <h1>Data</h1>
      <ul>
        {data.map(item => (
          <li key={item.id}>{item.name}</li>
        ))}
      </ul>
    </div>
  );
};

const mapStateToProps = (state) => ({
  data: state.data.items,
  loading: state.data.loading,
  error: state.data.error,
});

const mapDispatchToProps = {
  fetchData,
};

export default connect(mapStateToProps, mapDispatchToProps)(MyComponent);

详细解析

  • Action Creator: fetchData 返回一个对象,该对象具有 typepayload 属性。payload 是一个 Promise,这个 Promise 会自动处理并派发相应的状态 action。
  • Middleware: redux-promise-middleware 拦截所有 action,并检查它们的 payload 是否为 Promise。如果是,则在 Promise 的生命周期内自动派发 PENDING, FULFILLED, 和 REJECTED action。
  • Reducer: 根据 action 的类型更新 state。FETCH_DATA_PENDING 表示请求开始,FETCH_DATA_FULFILLED 表示请求成功并接收数据,FETCH_DATA_REJECTED 表示请求失败并接收错误。

自定义 Action Type 前缀

redux-promise-middleware 默认使用 PENDING, FULFILLED, 和 REJECTED 作为后缀。你可以通过配置来自定义这些后缀:

import promiseMiddleware from 'redux-promise-middleware';

const customMiddleware = promiseMiddleware({
  promiseTypeSuffixes: ['LOADING', 'SUCCESS', 'FAILURE'],
});

const store = createStore(
  rootReducer,
  applyMiddleware(customMiddleware)
);

这样,派发的 action 类型将变为 FETCH_DATA_LOADING, FETCH_DATA_SUCCESS, 和 FETCH_DATA_FAILURE

总结

  • 简化异步操作: redux-promise-middleware 通过处理基于 Promise 的异步操作,简化了 Redux 的异步逻辑。
  • 自动派发状态 action: 根据 Promise 的状态(pending, fulfilled, rejected),自动派发相应的 Redux action。
  • 灵活性: 提供了定制化的选项,可以根据项目需求调整 action 的前缀和后缀。

异步操作必须使用中间件吗

并不是必须的,比如我们要setTimeout之后更新state,大可以:


setTimeout(()=>{
    dispatch({type:'add',payload})
},3000)

中间件有显著优势:

  1. 代码可读性和可维护性:将所有的代码都放到 action creators中,组件只负责交互和渲染
  2. 实现关注点分离:将组件逻辑和异步逻辑进行分离
  3. 处理复杂的异步流程:如 redux-sagaredux-observable 提供了强大的工具来处理复杂的异步流程,例如并发请求、取消请求、以及处理多步骤的异步操作。这些中间件提供了比手动管理异步操作更高级的控制。