2016-04-02 5 views
14

私は現在、学習者のReact/Reduxアプリケーションを構築しています。サービスの依存性注入を行う方法については頭を悩ますことができません。reduxアクションクリエイターの依存性注入

具体的には:BluetoothService(第三者図書館を抽象化している)は、Bluetoothを使用して他のデバイスをスキャンして接続するためのものです。

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

(私はサンク・ミドルウェアを使用しています)

私の問題は、しかし、である。このサービスは、アクションの作成者がこのようなものを使用します:アクション作成者にサービス自体を注入する方法は?

私はこれが良いパターンではないと思うので、ハードコードされたrequire(またはimport)を望んでいません。私はまた、私のワークステーション(ブルートゥースを持っていない)でアプリケーションをテストしている間モックサービスを使用できるようにしたい - 環境に応じて、同じインターフェースを持つ別のサービスが自分のアクション作成者の中に注入されたい。これは静的インポートを使用するだけでは不可能です。

メソッド自体(12b)のパラメータとしてbluetoothServiceを作成しようとしましたが、メソッド自体を純粋に効果的にしましたが、アクションを使用して問題をコンテナに移動するだけです。すべてのコンテナはサービスについて知っていなければならず、(例えば、小道具を介して)注入の実装を取得する必要があります。 また、別のアクション内からアクションを使用したい場合、同じ問題が再び発生します。

ゴール: 私のアプリで使用する実装のブートストラップ時間を決めたいと思います。 これを行うための良い方法やベストプラクティスはありますか?

答えて

8

あなたは非同期アクションに応答しますredux middlewareを使用することができます。このように、あなたはどんなサービスを注入したり、1つの場所で必要なモック、およびアプリは任意の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])); 

あなたはstartDeviceScan非同期アクションを派遣、されるアクションタイプで関連するアクションの作成に使用されます。ミドルウェアは、アクションDEVICE_SCANを識別します。アクションにシンボルが含まれていない場合は、そのシンボルをストアに戻します(次のミドルウェア/レデューサー)。

シンボルDEVICE_SCANが存在する場合、ミドルウェアはアクションタイプを抽出し、開始アクションを作成してディスパッチし(例:ローディングスピナー用)、非同期リクエストを行い、成功または失敗アクションを作成してディスパッチします。

real world redux middle exampleもご覧ください。

+0

その詳細な回答に感謝します。私は前にミドルウェアについて読んだが、今までそれを完全に把握していなかった。これは、反応を還元するアプリケーションでサービスを使用するための標準またはベストプラクティスといえますか? – Charminbear

+0

ようこそ - 私もそれに苦労しました:) –

+0

ありがとう!これを使用して、角度依存性注入をreduxとredux-thunkとシームレスに連携させることができました。これにより、AngularからReactへの移行がよりスムーズになります:https://gist.github.com/jasononeil/e835aeb2ce7b9f099616ef2098caac1d –

2

アクションクリエイターを自分のサービスにラップすることはできますか?

export function actionCreatorsService(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)); 
     }); 
     } 
    } 

    return { 
     addDevice, 
     startDeviceScan 
    }; 
} 

ここで、このサービスのクライアントは、bluetoothServiceのインスタンスを提供する必要があります。

const bluetoothService = require('./actual/bluetooth/service'); 
const actionCreators = require('./actionCreators')(bluetoothService); 

そして、あなたのテストで:実際のsrcコードで

const mockBluetoothService = require('./mock/bluetooth/service'); 
const actionCreators = require('./actionCreators')(mockBluetoothService); 

あなたがアクションクリエイターの中に、Bluetoothサービスにあなたがアクションクリエイターをインポートする必要があるたびに指定したくない場合モジュールでは、通常のエクスポート(実際のブルートゥースサービスを使用する)とモックのエクスポート(モックサービスを使用する)を行うことができます。そして、呼び出し元のコードは次のようになります。

const actionCreators = require('./actionCreators').actionCreators; 

そして、あなたのテストコードは次のようになります。

const actionCreators = require('./actionCreators').mockActionCreators; 
+0

感謝を。 DIを使ったサービスとしてクリエイター全体をラップするのは良い解決策ですが、それは私が関数のDIとして試したときと同じ問題を残します。アクションクリエイターを使っているコンテナ(またはクライアント)は、使用するサービス。しかし、アプリケーション全体をブートストラップしている間に、すでに 'main.js'-Fileに既にあることを前に、私はその決定をしたいと思っています。あなたはその後、プロパティの周りに使用するサービスを渡しますか? – Charminbear

+0

私はJS用のDIライブラリ(Angular以外)を使用していませんが、これのために特別に構築されたライブラリを調査する価値があります。私が見た他の解決策は、環境変数を使用してエクスポートするものを決定することです(env変数が設定されている場合、bluetoothServiceモジュールが模擬サービスをエクスポートする可能性があります)。 –

13

React-thunkは、withExtraArgumentを使用して任意のオブジェクトをサンクに渡すことをサポートしています。あなたは、例えば、依存関係注入サービスオブジェクトにこれを使用することができます。

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

const services = { 
    bluetoothService: bluetoothService 
}; 

let store = createStore(reducers, {}, 
    applyMiddleware(thunk.withExtraArgument(services)) 
); 

その後のサービスは3番目の引数として、あなたのサンクに用意されています

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

これは、依存関係を使用してのように正式ではありませんAngular2のインジェクションデコレータや、別のReduxミドルウェアレイヤーを作成してサービスをサンクに渡すと、ちょっと醜い「何かのオブジェクト」に過ぎませんが、実装するのはかなり簡単です。

1

私は、その目的のためにredux-bubble-diと呼ばれる依存性注入ミドルウェアを作成しました。アクションクリエータに任意の数の依存関係を注入するために使用できます。

npm install --save redux-bubble-diまたはdownloadでインストールできます。 Reduxのバブルジを使用して

あなたの例では、次のようになります。迅速な答えを

//import { DiContainer } from "bubble-di"; 
const { DiContainer } = require("bubble-di"); 
//import { createStore, applyMiddleware } from "redux"; 
const { createStore, applyMiddleware } = require("redux"); 
//import reduxBubbleDi from "redux-bubble-di"; 
const reduxBubbleDi = require("redux-bubble-di").default; 

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

DiContainer.setContainer(new DiContainer()); 
DiContainer.getContainer().registerInstance("bluetoothService", bluetoothService); 

const store = createStore(
    state => state, 
    undefined, 
    applyMiddleware(reduxBubbleDi(DiContainer.getContainer())), 
); 

const startDeviceScan = { 
    bubble: (dispatch, bluetoothService) => { 
     bluetoothService.startDeviceSearch((device) => { 
      dispatch(addDevice(device)); 
     }); 
    }, 
    dependencies: ["bluetoothService"], 
}; 

// ... 

store.dispatch(startDeviceScan);