Skip to content

Commit 2df0793

Browse files
committed
ReduxのMiddlewareの実装について (#100)
* feat(redux): Dispatcherの実装と実行例を追加 * feat(redux): apply-middlewareのサンプルを追加 * chore(redux): redux と connect の違いについて * style(redux): fix lint error * fix(gitbook): fix to use gitbook 2
1 parent 554ff1d commit 2df0793

7 files changed

Lines changed: 126 additions & 37 deletions

File tree

book.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"gitbook": ">=2.5.2",
2+
"gitbook": "^2.5.2",
33
"title": "JavaScript Plugin Architecture",
44
"description": "JavaScriptライブラリやツールのプラグインの仕組み",
55
"githubId": "azu/JavaScript-Plugin-Architecture",
@@ -30,4 +30,4 @@
3030
"token": "UA-2184335-16"
3131
}
3232
}
33-
}
33+
}

ja/Redux/README.md

Lines changed: 62 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ Reduxには[Three Principles](http://redux.js.org/docs/introduction/ThreePrincip
1616
- StateはActionを経由しないと書き換えることができない
1717
- Changes are made with pure functions
1818
- Actionを受け取りStateを書き換えるReducerと呼ばれるpure functionを作る
19-
19+
2020
この三原則についての詳細はドキュメントなどを参照してください。
2121

2222
- [Read Me | Redux](http://redux.js.org/)
@@ -55,8 +55,10 @@ Reduxの例として次のようなコードを見てみます。
5555
dispatch(action) -> (_Middleware_ の処理) -> reducerにより新しいStateの作成 -> (Stateが変わったら) -> subscribeで登録したコールバックを呼ぶ
5656
```
5757

58+
- [ ] 図にしたほうがいい
59+
5860
次は _Middleware_ によりどのような拡張ができるのかを見ていきます。
59-
61+
6062
## Middleware
6163

6264
Reduxでは第三者が拡張できる仕組みを _Middleware_ と呼んでいます。
@@ -128,22 +130,72 @@ const middleware = (store) => {
128130

129131
[import, logger.js](../../src/Redux/logger.js)
130132

133+
- [ ] ログを挟んでいる図
134+
131135
この場合の `next``dispatch` と言い換えても問題ありませんが、複数の _Middleware_ を適応した場合は、
132136
**次の** _Middleware_ を呼び出すという事を表現しています。
133137

134-
Reduxの _Middleware_ の仕組みは単純ですが、見慣れないデザインであるために複雑であるように見えます
138+
Reduxの _Middleware_ の仕組みは単純ですが、見慣れないデザインなので複雑に見えます
135139
実際に同じ仕組みを実装しながら、Reduxの _Middleware_ について学んでいきましょう。
136140

137141
## どういう仕組み?
138142

139-
- 高階関数をapplyしている
140-
- http://rackt.github.io/redux/docs/advanced/Middleware.html
141-
- その機構のコードへのリンク
142-
- その仕組みやプラグインについてドキュメントへのリンク
143+
_Middleware_`dispatch`をラップする処理ですが、そもそも`dispatch`とはどういう事をしているのでしょうか?
144+
145+
簡潔に書くと、Reduxの`store.dispatch(action)``store.subscribe(callback)`で登録した`callback`に`actionを渡し呼び出すだけです。
146+
147+
これよくみるPub/Subのパターンですが、今回はこのPub/Subパターンの実装からみていきましょう。
148+
149+
### Dispatcher
150+
151+
[ESLint](../ESLint/README.md)と同様でEventEmitterを使い、`dispatch``subscribe`を持つ`Dispatcher`を実装すると以下のようになります。
152+
153+
[import, Dispatcher.js](../../src/Redux/Dispatcher.js)
154+
155+
`Dispatcher`はActionオブジェクトを`dispatch`すると、`subscribe`で登録されていたコールバック関数を呼び出すという単純なものです。
156+
157+
また、この`Dispatcher`の実装はReduxのものとは異なるので、あくまで理解のための参考実装です。
158+
159+
> Unlike Flux, Redux does not have the concept of a Dispatcher
160+
> This is because it relies on pure functions instead of event emitters
161+
> -- [Prior Art | Redux](http://redux.js.org/docs/introduction/PriorArt.html "Prior Art | Redux")
162+
163+
### applyMiddleware
164+
165+
次に、 _Middleware_ を適応する処理となる `applyMiddleware`を実装していきます。
166+
先ほども書いたように、 _Middleware_`dispatch` を拡張する仕組みです。
167+
168+
`applyMiddleware``dispatch`_Middleware_ を受け取り、 _Middleware_ で拡張した `dispatch` を返す関数です。
169+
170+
[import, apply-middleware.js](../../src/Redux/apply-middleware.js)
171+
172+
この`applyMiddleware`はReduxのものと同じなので、
173+
次のように _Middleware_ を適応した `dispatch` 関数を作成できます。
174+
175+
[import, apply-middleware-example.js](../../src/Redux/apply-middleware-example.js)
176+
177+
`applyMiddleware``timestamp`をActionに付加する _Middleware_ を適用しています。
178+
これにより`dispatchWithMiddleware(action)`した`action`には自動的に`timestamp`プロパティが追加されています。
179+
180+
```js
181+
const dispatchWithMiddleware = applyMiddleware(createLogger(), timestamp)(middlewareAPI);
182+
dispatchWithMiddleware({type: "FOO"});
183+
```
184+
185+
ここで _Middleware_ には`middlewareAPI`として定義した2つのメソッドを持つオブジェクトが渡されています。
186+
しかし、`getState`は読み込みのみで、_Middleware_にはStateを直接書き換える手段が用意されていません。
187+
また、もう1つの`dispatch`もActionオブジェクトを書き換えられますが、結局できることは`dispatch`するだけです。
188+
189+
この事から _Middleware_ にも三原則が適用されている事が分かります。
190+
191+
- State is read-only
192+
- StateはActionを経由しないと書き換えることができない
143193

144-
## 実装してみよう
194+
_Middleware_ という仕組み自体は[Connect](../connect/README.md)と似ています。
195+
しかし、 _Middleware_ が直接的に結果(State)を直接書き換える事はできません。
145196

146-
- [ ] DispatcherベースのMiddleware
197+
Connectの _Middleware_ は最終的な結果(`response`)を書き換えできます。
198+
一方、Reduxの _Middleware_ は扱える範囲が`dispatch`からReducerまでと線引されている違いと言えます。
147199

148200
## どういう事に向いてる?
149201

@@ -165,4 +217,4 @@ Reduxの _Middleware_ の仕組みは単純ですが、見慣れないデザイ
165217

166218

167219

168-
[Redux]: https://github.com/reactjs/redux "reactjs/redux: Predictable state container for JavaScript apps"
220+
[Redux]: https://github.com/reactjs/redux "reactjs/redux: Predictable state container for JavaScript apps"

src/Redux/Dispatcher-example.js

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
"use strict";
2+
import Dispatcher from "./Dispatcher";
3+
const dispatcher = new Dispatcher();
4+
dispatcher.subscribe(action => {
5+
console.log(action);
6+
/*
7+
{
8+
type: "ActionType"
9+
}
10+
*/
11+
});
12+
dispatcher.dispatch({
13+
type: "ActionType"
14+
});

src/Redux/Dispatcher.js

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,28 @@
1-
// LICENSE : MIT
21
"use strict";
3-
const assert = require("assert");
42
const EventEmitter = require("events");
53
export const ON_DISPATCH = "__ON_DISPATCH__";
64
/**
7-
* payload The payload object that must have `type` property.
8-
* @typedef {Object} DispatcherPayload
5+
* The action object that must have `type` property.
6+
* @typedef {Object} Action
97
* @property {String} type The event type to dispatch.
108
* @public
119
*/
1210
export default class Dispatcher extends EventEmitter {
1311
/**
14-
* add onAction handler and return unbind function
15-
* @param {function(DispatcherPayload)} payloadHandler
12+
* subscribe `dispatch` and call handler. it return release function
13+
* @param {function(Action)} actionHandler
1614
* @returns {Function} call the function and release handler
17-
* @public
1815
*/
19-
onDispatch(payloadHandler) {
20-
this.on(ON_DISPATCH, payloadHandler);
21-
return this.removeListener.bind(this, ON_DISPATCH, payloadHandler);
16+
subscribe(actionHandler) {
17+
this.on(ON_DISPATCH, actionHandler);
18+
return this.removeListener.bind(this, ON_DISPATCH, actionHandler);
2219
}
2320

2421
/**
2522
* dispatch action object.
26-
* StoreGroups receive this action and reduce state.
27-
* @param {DispatcherPayload} payload
28-
* @public
23+
* @param {Action} action
2924
*/
30-
dispatch(payload) {
31-
assert(payload !== undefined && payload !== null, "payload should not null or undefined");
32-
assert(typeof payload.type === "string", "payload's type should be string");
33-
this.emit(ON_DISPATCH, payload);
25+
dispatch(action) {
26+
this.emit(ON_DISPATCH, action);
3427
}
35-
3628
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
"use strict";
2+
import Dispatcher from "./Dispatcher";
3+
import applyMiddleware from "./apply-middleware";
4+
import timestamp from "./timestamp";
5+
import createLogger from "./logger";
6+
const dispatcher = new Dispatcher();
7+
dispatcher.subscribe(action => {
8+
console.log(action);
9+
/*
10+
{ timeStamp: 1463742440479, type: 'FOO' }
11+
*/
12+
});
13+
// Redux compatible middleware API
14+
const state = {};
15+
const middlewareAPI = {
16+
getState(){
17+
// shallow-copy state
18+
return Object.assign({}, state);
19+
},
20+
dispatch(action){
21+
dispatcher.dispatch(action);
22+
}
23+
};
24+
// create `dispatch` function that wrapped with middleware
25+
const dispatchWithMiddleware = applyMiddleware(createLogger(), timestamp)(middlewareAPI);
26+
dispatchWithMiddleware({type: "FOO"});

src/Redux/apply-middleware.js

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,16 @@
1-
// LICENSE : MIT
21
"use strict";
3-
// core -
4-
// next - next function
5-
// action - action object
2+
/*
3+
=> api - middleware api
4+
=> next - next/dispatch function
5+
=> action - action object
6+
7+
*/
68
const applyMiddlewares = (...middlewares) => {
79
return middlewareAPI => {
8-
const originalDispatch = middlewareAPI.dispatch.bind(middlewareAPI);
10+
const originalDispatch = (action) => {
11+
middlewareAPI.dispatch(action);
12+
};
13+
// `api` is middlewareAPI
914
const wrapMiddleware = middlewares.map(middleware => {
1015
return middleware(middlewareAPI);
1116
});

test/Redux/apply-middleware-test.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ describe("middleware", function () {
3030
const dispatchWithMiddleware = applyMiddleware(middleware)(middlewareAPI);
3131
const action = {type: "FOO"};
3232
// then
33-
dispatcher.onDispatch(payload => {
33+
dispatcher.subscribe(payload => {
3434
if(payload.type === "FOO"){
3535
state.isUpdated = true;
3636
}
@@ -44,7 +44,7 @@ describe("middleware", function () {
4444
const dispatchWithMiddleware = applyMiddleware(timestamp)(dispatcher);
4545
const action = {type: "FOO"};
4646
// then
47-
dispatcher.onDispatch(payload => {
47+
dispatcher.subscribe(payload => {
4848
assert(typeof payload.timeStamp === "number");
4949
done();
5050
});

0 commit comments

Comments
 (0)