Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit54a7a28

Browse files
author
mheiber
authored
Merge pull request#5 from mheiber/simplify-api
Simplify api
2 parents8241434 +50d7208 commit54a7a28

File tree

4 files changed

+62
-117
lines changed

4 files changed

+62
-117
lines changed

‎README.md‎

Lines changed: 37 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -2,153 +2,106 @@
22

33
![redux-machine](http://i63.tinypic.com/2igdbus_th.jpg)
44

5-
*A tiny library for creating state machines in Redux apps.*
6-
5+
*A tiny lib (12 lines) for creating state machines as swappable Redux reducers*
76

87
redux-machine enables you to create[reducers](http://redux.js.org/docs/basics/Reducers.html) that can transition between different "statuses." These are likes states in a[finite state machine](https://en.wikipedia.org/wiki/Finite-state_machine). The goal is for redux-machine to support complex workflows simply while keeping all state in the redux store. Keeping all state in the store is good because:
98

109
- redux-machine works with time-travel debugging. Time-travel debugging was the main[motivation for building redux itself](https://www.youtube.com/watch?v=xsSnOQynTHs).
1110
- Debugging is easy because information is in one place (the store).
1211
- Statuses such are queryable by the user interface. This is helpful if you want to show things to the user such as loading spinners to indicate status
1312

14-
redux-saga and redux-observable also make it easy to create workflows in redux apps. They are more beautiful and powerful than redux-machine, but can break the "all state is in the store" paradigm.
15-
1613
##Install
1714

1815
`npm install redux-machine --save`
1916

20-
>redux-machine internally uses Object.assign and Symbol, whichareES2015features. If you need to support older browsers, you can use a polyfill such as[core-js](https://github.com/zloirock/core-js#basic).
17+
>redux-machine internally uses Object.assign, whichis anES2015feature. If you need to support older browsers, you can use a polyfill such as[core-js](https://github.com/zloirock/core-js#basic).
2118
22-
##API
19+
##How to Use
2320

24-
-**`createMachine(reducerObject)`** returns a machine, which is itself a reducer
25-
26-
`reducerObject` is an object where the keys are statuses and the values are sub-reducers. For example, this code creates a reducer that acts like`initReducer` when the status is`INIT` and acts like`inProgressReducer` when the status is`IN_PROGRESS`:
21+
This is the entire API for redux-machine:
2722

2823
```js
24+
// entire API, no middleware required
25+
import {createMachine } =from'./index.js'
26+
2927
constfetchUsersReducer=createMachine({
3028
'INIT': initReducer,
3129
'IN_PROGRESS': inProgressReducer
3230
})
3331
```
3432

35-
`reducerObject` must have an`INIT` key. The value for the`INIT` key is the reducer for the starting status of the machine/reducer.
36-
37-
The reducer returned by`createMachine` adds a`STATUS` key to the store. That is, when the status is`INIT`, the value of`STATUS` is`INIT` and when the status is`IN_PROGRESS`, the value of`STATUS` is`IN_PROGRESS`. This is useful for debugging or for acting on the current status in the UI or in your API callers.
38-
39-
-**`become`** (symbol)
40-
The reducers that are the values of`reducersObject` can use`become` to transition the machine/reducer to a different status. For example, the following code in`initReducer` transitions`fetchUsersReducer` to the`IN_PROGRESS` status when the`` action is dispatched. At that point,`fetchUsersReducer` will act like`inProgressReducer` and the value of`STATUS` changes to`IN_PROGRESS`:
41-
42-
```js
43-
import {become }from'redux-machine'
44-
45-
...
46-
switch (action.type) {
47-
case'FETCH_USERS':
48-
returnObject.assign({}, state, {
49-
error:null,
50-
[become]:'IN_PROGRESS'
51-
})
52-
case default:
53-
return state
54-
}
55-
```
56-
57-
>redux-machine's`become` is influenced by[Akka's`become`](http://doc.akka.io/docs/akka/snapshot/scala/actors.html#become-unbecome).
58-
59-
##Full Example
33+
The reducer returned by`createMachine` will act like`initReducer` when its status is`INIT` and will act like`inProgressReducer` when the status is`IN_PROGRESS`. If the store's`state.status` is undefined, the reducer for`INIT` is used (so it's a good idea to provide a reducer for the`INIT` status).
34+
>>>>>>>update API
6035
61-
Suppose you are making an API call to fetch users, and want only one instance of this API call to happen at a time. You also want to communicate to the user the status of the API call:`INIT` (initial status, no call in progress) and`IN_PROGRESS`.
62-
63-
You can use redux-machine to have the following status transitions in your reducer:
64-
![status machine for the api-calling example](http://oi67.tinypic.com/qz57qd.jpg)
65-
- When the status is`INIT` and the action type is`FETCH_USERS`, the machine transitions to`IN_PROGRESS` status.
66-
- When the status is`IN_PROGRESS` and the action type is`FETCH_USERS_RESPONSE` or`FETCH_USERS_FAIL`, the machine transitions to the`INIT` (initial) status.
67-
68-
I'll walk through how to create the machine in this example. If you prefer to see all the code in one place, it's in`./test.js`.
69-
70-
###`import` redux-machine
71-
72-
```js
73-
import {createMachine,become }from'./index.js'
74-
```
75-
76-
###Create Reducers
77-
78-
Next, define a reducer for each status in the machine ('INIT' and 'IN_PROGRESS'):
36+
`initReducer` and`inProgressReducer` can do status transitions by setting`state.status`:
7937

8038
```js
8139
constinitReducer= (state= {error:null, users: []},action)=> {
82-
8340
switch (action.type) {
8441
case'FETCH_USERS':
8542
returnObject.assign({}, state, {
8643
error:null,
87-
[become]:'IN_PROGRESS'
44+
// transition to a different status!
45+
status:'IN_PROGRESS'
8846
})
8947
default:
9048
return state
9149
}
9250
}
9351

9452
constinProgressReducer= (state= {},action)=> {
95-
9653
switch (action.type) {
9754
case'FETCH_USERS_RESPONSE':
9855
returnObject.assign({}, state, {
9956
error:null,
10057
users:action.payload.users,
101-
[become]:'INIT'
58+
// transition to a different status!
59+
status:'INIT'
10260
})
10361
case'FETCH_USERS_FAIL':
10462
returnObject.assign({}, state, {
105-
error:'fail',
106-
[become]:'INIT'
63+
error:action.payload.error,
64+
// transition to a different status!
65+
status:'INIT'
10766
})
10867
default:
10968
return state
11069
}
11170
}
11271
```
11372

114-
>The only special part of these reducers is how they use the`become` symbol, which I'll explain in the next section.
115-
116-
###Create the Machine
73+
The example above defines the following state machine:
11774

118-
Here's how you can use the`createMachine` function to create a reducer from the`initReducer` and`inProgressReducer`:
119-
120-
```js
121-
constfetchUsersReducer=createMachine({
122-
'INIT': initReducer,
123-
'IN_PROGRESS': inProgressReducer
124-
})
125-
```
75+
![status machine for the api-calling example](http://oi67.tinypic.com/qz57qd.jpg)
12676

127-
>`fetchUsersReducer` is a normal[reducer](http://redux.js.org/docs/basics/Reducers.html) that you can use directly in your Redux app, with no need for special middleware.
77+
In words:
78+
- When the status is`INIT` and the action type is`FETCH_USERS`, the machine transitions to`IN_PROGRESS` status.
79+
- When the status is`IN_PROGRESS` and the action type is`FETCH_USERS_RESPONSE` or`FETCH_USERS_FAIL`, the machine transitions to the`INIT` (initial) status.
12880

129-
`initReducer` is the value for`INIT`, so`createMachine` knows to start off acting like`initReducer`.
81+
##Making Finite State Machine Reducers without a Library
13082

131-
In the object provided to`createMachine`, the names of the keys matter because they are used within reducers for transitioningthemachine/reducer between statuses. For example, the following codein`initReducer` says that whentheaction is of type`FETCH_USERS`, transition the machine to the`IN_PROGRESS` status:
83+
You don't need redux-machine, since you can accomplish almostthesame thing asin theexample above by defining`fetchUsersReducer` as follows:
13284

13385
```js
134-
switch (action.type) {
135-
case'FETCH_USERS':
136-
returnObject.assign({}, state, {
137-
error:null,
138-
[become]:'IN_PROGRESS'
139-
})
140-
casedefault:
141-
return state
86+
constfetchUsersReducer= (state,action)=> {
87+
switch (state.status) {
88+
case'INIT':
89+
returninitReducer(state, action)
90+
case'IN_PROGRESS':
91+
returninProgressReducer(state, action)
92+
default:
93+
returninitReducer(state, action)
14294
}
95+
}
14396
```
14497

145-
>`become` is a symbol imported fromredux-machine, not a string. Seethe`import` statement in the example above.
98+
The (marginal) advantages of usingredux-machine over just usingtheFSM pattern is that you can more clearly express intent and write slightly less code.
14699

147100
##Asynchronous Effects
148101

149-
redux-machinedoes only one thing: help you model explicit status transitions in reducers. Itdoesn't prescribe a way of handling asynchronous effects such as API calls. This leaves it open for you to use[no async effects library](http://stackoverflow.com/a/34599594/2482570),[redux-loop](https://github.com/redux-loop/redux-loop),[redux-thunk](https://github.com/gaearon/redux-thunk),[redux-saga](https://github.com/yelouafi/redux-saga), or anything else.
102+
redux-machine doesn't prescribe a way of handling asynchronous effects such as API calls. This leaves it open for you to use[no async effects library](http://stackoverflow.com/a/34599594/2482570),[redux-loop](https://github.com/redux-loop/redux-loop),[redux-thunk](https://github.com/gaearon/redux-thunk),[redux-saga](https://github.com/yelouafi/redux-saga), or anything else.
150103

151-
That said, redux-machine fits very naturally withredux-loop, since both enhancehow you can usereducers. Here's how you could use redux-machine with redux-loop:
104+
That said, redux-machine fits very naturally withother tools which enhancethe expressiveness ofreducers, such as redux-loop and redux-side-effect. Here's how you could use redux-machine with redux-loop:
152105

153106
```js
154107
import {loop,Effects }from'redux-loop'
@@ -166,7 +119,7 @@ const initReducer = (state = {error: null, users: []}, action) => {
166119
returnloop(
167120
// return the next state of the store
168121
// and transition to the IN_PROGRESS status
169-
Object.assign({}, state, {error:null,[become]:'IN_PROGRESS'}),
122+
Object.assign({}, state, {error:null,status:'IN_PROGRESS'}),
170123
// pass getUsers to the redux-loop middleware
171124
// The redux-loop middleware will call getUsers(), which
172125
// will dispatch a 'FETCH_USERS_RESPONSE' action

‎index.js‎

Lines changed: 8 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
"use strict"
22

3-
varbecome=Symbol('become')
4-
53
varcreateMachine=function(reducersObject){
6-
varstatus='INIT'
7-
varcurrentReducer=reducersObject['INIT']
8-
if(!currentReducer){
9-
thrownewError('reducersObject must have INIT reducer')
10-
}
114
returnfunction(state,action){
12-
varnextState=currentReducer(state,action)
13-
if(nextState[become]){
14-
status=nextState[become]
15-
currentReducer=reducersObject[status]
16-
}
17-
varnextStateWithStatus=Object.assign({},state,nextState,{status:status})
18-
returnnextStateWithStatus
5+
varstatus=(state&&state.status) ?state.status :'INIT'
6+
varreducer=reducersObject[status]
7+
if(!reducer){
8+
thrownewError('reducersObject missing reducer forstatus '+status)
9+
}
10+
constnextState=reducer(state,action)
11+
returnObject.assign({},nextState,{'status':nextState.status||status})
1912
}
2013
}
2114

22-
module.exports={createMachine:createMachine,become:become}
15+
module.exports={createMachine:createMachine,become:'status'/* for backwards compatibility */}

‎package.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name":"redux-machine",
33
"version":"1.0.1",
4-
"description":"State Machine Helper for Redux",
4+
"description":"a tiny lib forcreating state machines as swappableRedux reducers",
55
"main":"index.js",
66
"scripts": {
77
"test":"node test.js"

‎test.js‎

Lines changed: 16 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,18 @@
11
"use strict"
22

3-
const{ createMachine, become}=require('./index.js')
3+
const{ createMachine}=require('./index.js')
44
constassert=require('assert')
55

66
// BEGIN FIXTURES
77

88
constusers=['userFoo','userBar','userBaz']
99

10-
constinitReducer=(state={error:null,users:[]},action)=>{
11-
10+
constinitReducer=(state={},action)=>{
1211
switch(action.type){
1312
case'FETCH_USERS':
1413
returnObject.assign({},state,{
1514
error:null,
16-
[become]:'IN_PROGRESS'
15+
status:'IN_PROGRESS'
1716
})
1817
default:
1918
returnstate
@@ -27,12 +26,12 @@ const inProgressReducer = (state = {}, action) => {
2726
returnObject.assign({},state,{
2827
error:null,
2928
users:action.payload.users,
30-
[become]:'INIT'
29+
status:'INIT'
3130
})
3231
case'FETCH_USERS_FAIL':
3332
returnObject.assign({},state,{
3433
error:action.payload,
35-
[become]:'INIT'
34+
status:'INIT'
3635
})
3736
default:
3837
returnstate
@@ -65,9 +64,7 @@ const expect = (expected, maybeMessage) => assert.deepEqual(state, expected, may
6564

6665
action('DUMMY')
6766
expect({
68-
error:null,
6967
status:'INIT',
70-
users:[]
7168
},'Should set initial status to "INIT"')
7269

7370
action('FETCH_USERS_RESPONSE',{users})
@@ -76,22 +73,19 @@ expect(prevState, 'Should ignore messages when not handled by current status')
7673
action('FETCH_USERS')
7774
expect({
7875
error:null,
79-
status:'IN_PROGRESS',
80-
users:[]
76+
status:'IN_PROGRESS'
8177
})
8278

8379
action('FETCH_USERS_FAIL','timeout')
8480
expect({
8581
error:'timeout',
86-
status:'INIT',
87-
users:[]
82+
status:'INIT'
8883
})
8984

9085
action('FETCH_USERS')
9186
expect({
9287
error:null,
93-
status:'IN_PROGRESS',
94-
users:[]
88+
status:'IN_PROGRESS'
9589
})
9690

9791
action('FETCH_USERS')
@@ -105,9 +99,14 @@ expect({
10599
})
106100

107101
assert.throws(
108-
()=>createMachine({}),
109-
err=>err.message==='reducersObject must have INIT reducer',
110-
'should error when reducersObject missing "INIT"'
102+
()=>{
103+
letstore={status:'STATUS_NOT_IN_CREATE_MACHINE'}
104+
constreducer=createMachine({})
105+
store=reducer(store,{type:'DUMMY'})
106+
107+
},
108+
err=>err.message==='reducersObject missing reducer for status STATUS_NOT_IN_CREATE_MACHINE',
109+
'should error when status not found'
111110
)
112111

113112
console.log('success')

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp