Redux中的Enhancer 与 Middleware

1. store enhancer

  • Store Enhancer基本结构
    Store Enhancer是一个函数,这个函数可以接受一个createStore模样的函数为参数,返回一个新的createStore函数。它的主要目的是改变和加强store的接口方法。
const doNothingEnhancer = (createStore) => (reducer, preloadedState, enhancer) => {
  const store = createStore(reducer, prloadedStat, enhancer);
  return store;
};

上面的例子中,最里层的函数体可以拿到一个createStore函数对象,还有应该传递给这个creatStore函数对象
的三个参数。所以基本的套路就是利用所给的参数创造出一个store对象,然后定制store对象,最后把store
对象返回出去。

  • 实现Store Enhancer
    实现一个Store Enhancer, 在于如何定制产生的Store对象,一个store对象包含下列接口:
    dispatch, subscribe, getState, replaceReducer, 每一个接口都可以被修改,无论怎么修改最后
    都要调用原有对应的函数。

记录action

const logEnhancer = (createStore) => (reducer, preloadedState, enhancer) => {
  const store = createStore(reducer, prloadedStat, enhancer);
  const originalDispatch = store.dispatch;
  store.dispatch = (action) => {
    console.log('dipatch action: ', action);
    originalDispatch(action);
  }
  return store;
};

Store Enhancer通常都使用这样的模式,将store上某个函数的引用保存下来,给这个函数一个新的实现,
但是在完成增强功能之后,还是要调用原有的函数,保持原有的功能。

在store中增加reset方法

const RESET_ACTION_TYPE = '@@RESET';
const resetReducerCreator = (reducer, resetState) => (state, action) => {
  if (action.type === RESET_ACTION_TYPE){
    return resetState;
  } else {
    return reducer(state, action);
  }
}

const reset = (createStore) => (reducer, preloadedState, enhancer) => {
  const store = createStore(reducer, preloadedState, enhancer);
  const reset = (resetReducer, resetState) => {
    const newReducer = resetReducerCreator(resetReducer, resetState);
    store.replaceReducer(newReducer);
    store.dispatch({type: RESET_ACTION_TYPE, state: resetState});
  };
  
  return {
    ...store,
    reset
  };
}

export default reset;
  • 使用Store Enhancer
import { createStore, compose } from 'redux'
import rootReducer from './reducer'
import {
  sayHiOnDispatch,
  includeMeaningOfLife
} from './exampleAddons/enhancers'

const composedEnhancer = compose(sayHiOnDispatch, includeMeaningOfLife)

const store = createStore(rootReducer,  composedEnhancer)

export default store

2. 中间件(Middleware)

Middleware是一种特殊的enhancer,旨在加强和改变dispatch的行为,在action到达reducer之前提供
扩展功能,比如进行异步或者其他副作用操作。
中间件接口:

function doNothingMiddleware({dispatch, getState}) {
  return function(next){
    return function(action){
      return next(action);
    }
  }
}

在中间件函数里可以:

  • 调用dispatch派发一个action对象

  • 调用getState获得当前Redux Store 上的状态

  • 调用next告诉Redux当前中间件工作完毕,让Redux调用下一个中间件

  • 访问action对象action上的所有数据

  • 用中间件创建store

import { createStore, applyMiddleware } from 'redux'
import rootReducer from './reducer'
import { print1, print2, print3 } from './exampleAddons/middleware'

const middlewareEnhancer = applyMiddleware(print1, print2, print3)

// Pass enhancer as the second arg, since there's no preloadedState
const store = createStore(rootReducer, middlewareEnhancer)

export default store
  • 编写自定义中间件
    ES5 functions
// Outer function:
function exampleMiddleware(storeAPI) {
  return function wrapDispatch(next) {
    return function handleAction(action) {
      // Do anything here: pass the action onwards with next(action),
      // or restart the pipeline with storeAPI.dispatch(action)
      // Can also use storeAPI.getState() here

      return next(action)
    }
  }
}

ES6 arrow functions

const anotherExampleMiddleware = storeAPI => next => action => {
  // Do something in here, when each action is dispatched
  return next(action)
}

3. 异步函数中间件

  • 自定义异步函数中间件
const asyncFunctionMiddleware = storeAPI => next => action => {
  // If the "action" is actually a function instead...
  if (typeof action === 'function') {
    // then call the function and pass `dispatch` and `getState` as arguments
    return action(storeAPI.dispatch, storeAPI.getState)
  }
  // Otherwise, it's a normal action - send it onwards
  return next(action)
}

const middlewareEnhancer = applyMiddleware(asyncFunctionMiddleware)
const store = createStore(rootReducer, middlewareEnhancer)

// Write a function that has `dispatch` and `getState` as arguments
const fetchSomeData = (dispatch, getState) => {
  // Make an async HTTP request
  client.get('todos').then(todos => {
    // Dispatch an action with the todos we received
    dispatch({ type: 'todos/todosLoaded', payload: todos })
    // Check the updated store state after dispatching
    const allTodos = getState().todos
    console.log('Number of todos after loading: ', allTodos.length)
  })
}

// Pass the _function_ we wrote to `dispatch`
store.dispatch(fetchSomeData)
// logs: 'Number of todos after loading: ###'
  • 使用redux-thunk
    redux-thunk 实现:
function createThunkMiddleware(extraArguments){
  return ({dispatch, getState}) => next => action => {
    if(typeof action === 'function') {
      return action(dispatch, getState, extraArguments);
    }
    return next(action);
  };
}
const thunk = createThunkMiddleware();
export default thunk;

配置中间件

import { createStore, applyMiddleware } from 'redux'
import thunkMiddleware from 'redux-thunk'
import { composeWithDevTools } from 'redux-devtools-extension'
import rootReducer from './reducer'

const composedEnhancer = composeWithDevTools(applyMiddleware(thunkMiddleware))

// The store now has the ability to accept thunk functions in `dispatch`
const store = createStore(rootReducer, composedEnhancer)
export default store

使用redux-thunk创建异步action

export const fetchWeatherStarted = () => ({
  type: FETCH_STARTED
});

export const fetchWeatherSuccess = (result) => ({
  type: FETCH_SUCCESS,
  result
});

export const fetchWeatherFailure = (error) => ({
  type: FETCH_FAILURE,
  error
});

export const fetchWeather = (cityCode) => {
  return (dispatch) => {
    const apiUrl = `/data/cityinfo/${cityCode}.html`;
    dispatch(fetchWeatherStarted());
    fetch(apiUrl).then((response)=>{
      if(response.status !== 200) {
        throw new Error('Fail to get response with status ' + response.status);
      }
      response.json().then((responseJson) => {
        dispatch(fetchWeatherSuccess(responseJson.weatherinfo));
      }).catch((error)=>{
        throw new Error('Invalid json response: ' + error);
      });
    }).catch(error) => {
      dispatch(fetchWeatherFailure(error));
    });
  };
}
  • 使用Promise中间件
    实现方式一
function isPromise(obj){
  return obj && typeof obj.then === 'function';
}
export default function promiseMiddleware({dispatch}) {
  return function(next) {
    return function(action){
      return isPromise(action)? action.then(dispatch) : next(action);
    }
  }
}

实现方式二

/**
 * action 对象类似下面的格式
 * {
 *   promise: fetch(apiUrl),
 *   types: ['pending', 'success', 'failure']
 * }
 */
export default function promiseMiddleware({dispatch}) {
  return (next) => (action) => {
    const {types, promise, ...rest} = action;
    if(!isPromise(promise) || !(action.types && action.types.length === 3)){
      return next(action);
    }

    const {PENDING, DONE, FAIL] = types;
    dispatch({...rest, type: PENDING});
    return action.promise.then(
      (result) => dispatch({...rest, result, type: DONE}),
      (error) => dispatch({...rest, error, type: FAIL}),
    );
  };
}
posted @ 2023-02-24 20:34  箫笛  阅读(204)  评论(0)    收藏  举报