redux动作创建器中的依赖注入

Cha*_*ear 20 javascript dependency-injection reactjs redux

我目前正在构建一个学习者React/Redux应用程序,我无法解决如何为服务进行依赖注入.

更具体一点:我有一个BluetoothService(抽象第三方库)扫描并通过蓝牙连接到其他设备.该服务被动作创建者利用,如下所示:

deviceActionCreators.js:

const bluetoothService = require('./blueToothService')
function addDevice(device) {
   return { type: 'ADD_DEVICE', device }
}

function startDeviceScan() {
   return function (dispatch) {
      // The Service invokes the given callback for each found device
      bluetoothService.startDeviceSearch((device) => {
          dispatch(addDevice(device));
      });
   }
}
module.exports = { addDevice, startDeviceScan };
Run Code Online (Sandbox Code Playgroud)

(我正在使用thunk-middleware)

我的问题是:如何将服务本身注入动作创建者?

我不希望硬编码require(或import在ES6中),因为我认为这不是一个好的模式 - 除了使测试更加困难之外.我还希望能够在我的工作站(没有蓝牙)上测试应用程序时使用模拟服务 - 所以根据环境我希望在我的action-creator中注入相同界面的其他服务.使用静态导入是不可能的.

我已经尝试将bluetoothService作为Method本身的一个参数(startDeviceScan(bluetoothService){}) - 有效地使方法本身是纯粹的 - 但这只是使用动作将问题移动到容器.每个容器都必须知道该服务然后获得它的实现(例如通过props).另外,当我想在另一个动作中使用动作时,我又会遇到同样的问题.

目标:我想决定在我的应用程序中使用哪些实现的自举时间.这样做是否有好方法或最佳实践?

mik*_*dge 19

React-thunk支持使用将任意对象传递给thunk withExtraArgument.您可以使用它来依赖注入服务对象,例如:

const bluetoothService = require('./blueToothService');

const services = {
    bluetoothService: bluetoothService
};

let store = createStore(reducers, {},
    applyMiddleware(thunk.withExtraArgument(services))
);
Run Code Online (Sandbox Code Playgroud)

然后作为第三个参数,您的thunk可以使用这些服务:

function startDeviceScan() {
    return function (dispatch, getstate, services) {
        // ...
        services.bluetoothService.startDeviceSearch((device) => {
            dispatch(addDevice(device));
        });
    }
}
Run Code Online (Sandbox Code Playgroud)

这不像在Angular2中使用依赖注入装饰器或创建一个单独的Redux中间件层来将服务传递给thunks那样正式 - 它只是一个有点难看的"任何对象" - 但另一方面它是实施起来相当简单.


Ori*_*ori 11

您可以使用将响应异步操作的redux中间件.通过这种方式,您可以在一个地方注入您需要的任何服务或模拟,并且应用程序将没有任何api实现细节:

// bluetoothAPI Middleware
import bluetoothService from 'bluetoothService';

export const DEVICE_SCAN = Symbol('DEVICE_SCAN'); // the symbol marks an action as belonging to this api

// actions creation helper for the middleware
const createAction = (type, payload) => ({ 
    type,
    payload
});

// This is the export that will be used in the applyMiddleware method
export default store => next => action => {
    const blueToothAPI = action[DEVICE_SCAN];

    if(blueToothAPI === undefined) {
        return next(action);
    }

    const [ scanDeviceRequest, scanDeviceSuccess, scanDeviceFailure ] = blueToothAPI.actionTypes;

    next(createAction(scanDeviceRequest)); // optional - use for waiting indication, such as spinner

    return new Promise((resolve, reject) => // instead of promise you can do next(createAction(scanDeviceSuccess, device) in the success callback of the original method
        bluetoothService.startDeviceSearch((device) => resolve(device), (error) = reject(error)) // I assume that you have a fail callback as well
        .then((device) => next(createAction(scanDeviceSuccess, device))) // on success action dispatch
        .catch((error) => next(createAction(scanDeviceFailure, error ))); // on error action dispatch
};

// Async Action Creator
export const startDeviceScan = (actionTypes) => ({
    [DEVICE_SCAN]: {
        actionTypes
    }
});

// ACTION_TYPES
export const SCAN_DEVICE_REQUEST = 'SCAN_DEVICE_REQUEST'; 
export const SCAN_DEVICE_SUCCESS = 'SCAN_DEVICE_SUCCESS'; 
export const SCAN_DEVICE_FAILURE = 'SCAN_DEVICE_FAILURE';

// Action Creators - the actions will be created by the middleware, so no need for regular action creators

// Applying the bluetoothAPI middleware to the store
import { createStore, combineReducers, applyMiddleware } from 'redux'
import bluetoothAPI from './bluetoothAPI';

const store = createStore(
  reducers,
  applyMiddleware(bluetoothAPI);
);

// Usage
import { SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE } from 'ACTION_TYPES';

dispatch(startDeviceScan([SCAN_DEVICE_REQUEST, SCAN_DEVICE_SUCCESS, SCAN_DEVICE_FAILURE]));
Run Code Online (Sandbox Code Playgroud)

您将使用将在创建相关操作时使用的操作类型调度startDeviceScan异步操作.中间件通过符号DEVICE_SCAN识别动作.如果操作不包含该符号,则会将其发送回商店(下一个中间件/ reducer).

如果符号DEVICE_SCAN存在,则中间件提取操作类型,创建和调度启动操作(例如,对于加载微调器),发出异步请求,然后创建并分派成功或失败操作.

另外看看现实世界的redux中间例子.

  • 非常感谢这么详细的回答。我之前读过中间件,但直到现在才完全掌握它。您认为这是在 react-redux 应用程序中使用服务的标准或最佳实践吗? (2认同)