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

Commit4859392

Browse files
authored
Merge pull request#11 from rjz/refactor/thunk-middleware
Refactor using thunk middleware
2 parentsdf6709d +7c3f4b1 commit4859392

File tree

10 files changed

+179
-113
lines changed

10 files changed

+179
-113
lines changed

‎README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ Related articles:
99
2.[TypeScript and Redux: adding React][article-2]
1010
3.[TypeScript and Redux: async actions][article-3]
1111
4.[TypeScript and Redux: unit testing with Jest][article-4]
12+
5.[Integrating TypeScript and redux-thunk][article-5]
1213

1314
##Usage
1415

@@ -30,3 +31,4 @@ Install and build demo:
3031
[article-2]:https://rjzaworski.com/2016/08/typescript-redux-and-react
3132
[article-3]:https://rjzaworski.com/2016/09/typescript-redux-async-actions
3233
[article-4]:https://rjzaworski.com/2016/12/testing-typescript-with-jest
34+
[article-5]:https://rjzaworski.com/2017/01/typescript-redux-thunk

‎package.json‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,8 @@
2222
"dependencies": {
2323
"react":"15.4.1",
2424
"react-redux":"4.4.6",
25-
"redux":"3.6.0"
25+
"redux":"3.6.0",
26+
"redux-thunk":"^2.2.0"
2627
},
2728
"devDependencies": {
2829
"@types/react":"0.14.55",

‎src/__mocks__/api.ts‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
exportconstapi={
2+
save:jest.fn(),
3+
load:jest.fn(),
4+
}
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
jest.mock('../../api')
2+
3+
import{createStore}from'redux'
4+
import{api}from'../../api'
5+
import*asactionsfrom'../index'
6+
7+
describe('actions',()=>{
8+
conststore=()=>{
9+
constreducer=jest.fn()
10+
const{ dispatch}=createStore(reducer)
11+
reducer.mockReset()// ignore @@redux/INIT
12+
return{ dispatch, reducer}
13+
}
14+
15+
consteventually=(assertFn)=>
16+
newPromise((resolve,reject)=>{
17+
setTimeout(()=>{
18+
try{
19+
assertFn()
20+
}catch(e){
21+
returnreject(e)
22+
}
23+
resolve()
24+
},1)
25+
})
26+
27+
constexpectTypes=(reducer,types)=>
28+
()=>
29+
expect(reducer.mock.calls.map(x=>x[1].type)).toEqual(types)
30+
31+
describe('.saveCount',()=>{
32+
beforeEach(()=>{
33+
api.save.mockReturnValue(Promise.resolve(null))
34+
})
35+
36+
it('sends an API request',()=>{
37+
actions.saveCount({value:'14'})(jest.fn())
38+
expect(api.save.mock.calls.length).toEqual(1)
39+
})
40+
41+
describe('when API request succeeds',()=>{
42+
it('dispatches SAVE_COUNT_SUCCESS',()=>{
43+
const{ dispatch, reducer}=store()
44+
actions.saveCount({value:14})(dispatch)
45+
returneventually(expectTypes(reducer,[
46+
'SAVE_COUNT_REQUEST',
47+
'SAVE_COUNT_SUCCESS',
48+
]))
49+
})
50+
})
51+
52+
describe('when API request fails',()=>{
53+
beforeEach(()=>{
54+
api.save.mockReturnValue(Promise.reject(newError('something terrible happened')))
55+
})
56+
57+
it('dispatches SAVE_COUNT_ERROR',()=>{
58+
const{ dispatch, reducer}=store()
59+
actions.saveCount({value:14})(dispatch)
60+
returneventually(expectTypes(reducer,[
61+
'SAVE_COUNT_REQUEST',
62+
'SAVE_COUNT_ERROR',
63+
]))
64+
})
65+
66+
it('includes error message with SAVE_COUNT_ERROR',()=>{
67+
const{ dispatch, reducer}=store()
68+
actions.saveCount({value:14})(dispatch)
69+
returneventually(()=>{
70+
expect(reducer.mock.calls[1][1].error.message)
71+
.toEqual('something terrible happened')
72+
})
73+
})
74+
75+
it('includes request with SAVE_COUNT_ERROR for convenience',()=>{
76+
const{ dispatch, reducer}=store()
77+
actions.saveCount({value:14})(dispatch)
78+
returneventually(()=>{
79+
expect(reducer.mock.calls[1][1].request).toEqual({value:14})
80+
})
81+
})
82+
})
83+
})
84+
85+
describe('.loadCount',()=>{
86+
beforeEach(()=>{
87+
api.load.mockReturnValue(Promise.resolve({value:14}))
88+
})
89+
90+
it('sends an API request',()=>{
91+
actions.loadCount()(jest.fn())
92+
expect(api.load.mock.calls.length).toEqual(1)
93+
})
94+
95+
describe('when API request succeeds',()=>{
96+
it('dispatches LOAD_COUNT_SUCCESS',()=>{
97+
const{ dispatch, reducer}=store()
98+
actions.loadCount()(dispatch)
99+
returneventually(expectTypes(reducer,[
100+
'LOAD_COUNT_REQUEST',
101+
'LOAD_COUNT_SUCCESS',
102+
]))
103+
})
104+
105+
it('includes new value with LOAD_COUNT_SUCCESS',()=>{
106+
const{ dispatch, reducer}=store()
107+
actions.loadCount()(dispatch)
108+
returneventually(()=>{
109+
expect(reducer.mock.calls[1][1].response).toEqual({value:14})
110+
})
111+
})
112+
})
113+
114+
describe('when API request fails',()=>{
115+
beforeEach(()=>{
116+
api.load.mockReturnValue(Promise.reject(newError('something terrible happened')))
117+
})
118+
119+
it('dispatches LOAD_COUNT_ERROR',()=>{
120+
const{ dispatch, reducer}=store()
121+
actions.loadCount()(dispatch)
122+
returneventually(expectTypes(reducer,[
123+
'LOAD_COUNT_REQUEST',
124+
'LOAD_COUNT_ERROR',
125+
]))
126+
})
127+
128+
it('includes error message with LOAD_COUNT_ERROR',()=>{
129+
const{ dispatch, reducer}=store()
130+
actions.loadCount()(dispatch)
131+
returneventually(()=>{
132+
expect(reducer.mock.calls[1][1].error.message)
133+
.toEqual('something terrible happened')
134+
})
135+
})
136+
})
137+
})
138+
})

‎src/actions/index.ts‎

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
import*asreduxfrom'redux'
2+
3+
import{api}from'../api'
4+
import{Store}from'../reducers/index'
5+
16
typeQ<T>={request:T}
27
typeS<T>={response:T}
38
typeE={error:Error}
@@ -34,7 +39,7 @@ export type ApiActionGroup<_Q, _S> = {
3439
error:(e:Error,q?:_Q)=>Action&Q<_Q>&E
3540
}
3641

37-
exportconstsaveCount:ApiActionGroup<{value:number},{}>={
42+
const_saveCount:ApiActionGroup<{value:number},{}>={
3843
request:(request)=>
3944
({type:'SAVE_COUNT_REQUEST', request}),
4045
success:(response,request)=>
@@ -43,11 +48,25 @@ export const saveCount: ApiActionGroup<{ value: number }, {}> = {
4348
({type:'SAVE_COUNT_ERROR', request, error}),
4449
}
4550

46-
exportconstloadCount:ApiActionGroup<null,{value:number}>={
51+
const_loadCount:ApiActionGroup<null,{value:number}>={
4752
request:(request)=>
4853
({type:'LOAD_COUNT_REQUEST',request:null}),
4954
success:(response,request)=>
5055
({type:'LOAD_COUNT_SUCCESS',request:null, response}),
5156
error:(error,request)=>
5257
({type:'LOAD_COUNT_ERROR',request:null, error}),
5358
}
59+
60+
typeapiFunc<Q,S>=(q:Q)=>Promise<S>
61+
62+
functionapiActionGroupFactory<Q,S>(x:ApiActionGroup<Q,S>,go:apiFunc<Q,S>){
63+
return(request:Q)=>(dispatch:redux.Dispatch<Store.All>)=>{
64+
dispatch(x.request(request))
65+
go(request)
66+
.then((response)=>dispatch(x.success(response,request)))
67+
.catch((e:Error)=>dispatch(x.error(e,request)))
68+
}
69+
}
70+
71+
exportconstsaveCount=apiActionGroupFactory(_saveCount,api.save)
72+
exportconstloadCount=apiActionGroupFactory(_loadCount,api.load)

‎src/components/counter.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import { connect } from 'react-redux'
44

55
import{
66
incrementCounter,
7-
saveCount,
87
loadCount,
8+
saveCount,
99
}from'../actions'
1010

1111
import{Store}from'../reducers'
@@ -38,9 +38,9 @@ const mapDispatchToProps = (dispatch: redux.Dispatch<Store.All>): ConnectedDispa
3838
increment:(n:number)=>
3939
dispatch(incrementCounter(n)),
4040
load:()=>
41-
dispatch(loadCount.request()),
41+
dispatch(loadCount(null)),
4242
save:(value:number)=>
43-
dispatch(saveCount.request({ value})),
43+
dispatch(saveCount({ value})),
4444
})
4545

4646
classCounterComponentextendsReact.Component<ConnectedState&ConnectedDispatch&OwnProps,{}>{

‎src/index.tsx‎

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import * as React from 'react' // tslint:disable-line
22
import*asReactDOMfrom'react-dom'
33
import*asreduxfrom'redux'
44
import{Provider}from'react-redux'
5+
importthunkfrom'redux-thunk'
56

67
import{
78
reducers,
@@ -10,14 +11,12 @@ import {
1011

1112
import{Counter}from'./components/counter'
1213

13-
import{apiMiddleware}from'./middleware'
14-
15-
constmiddleware=redux.applyMiddleware(
16-
apiMiddleware
14+
letstore:redux.Store<Store.All>=redux.createStore(
15+
reducers,
16+
{}asStore.All,
17+
redux.applyMiddleware(thunk),
1718
)
1819

19-
letstore:redux.Store<Store.All>=redux.createStore(reducers,{}asStore.All,middleware)
20-
2120
// Commented out ("let HTML app be HTML app!")
2221
window.addEventListener('DOMContentLoaded',()=>{
2322
constrootEl=document.getElementById('redux-app-root')

‎src/middleware/__tests__/index_spec.ts‎

Lines changed: 0 additions & 67 deletions
This file was deleted.

‎src/middleware/index.ts‎

Lines changed: 0 additions & 31 deletions
This file was deleted.

‎src/reducers/__tests__/index_spec.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import { createStore } from 'redux'
33
import{reducers}from'../index'
44
import{
55
incrementCounter,
6-
loadCount,
76
}from'../../actions'
87

98
describe('reducers/counter',()=>{
@@ -30,7 +29,9 @@ describe('reducers/counter', () => {
3029
expect(counter.value).toEqual(14)
3130
done()
3231
})
33-
store.dispatch(loadCount.success({value:14}))
32+
store.dispatch({
33+
type:'LOAD_COUNT_SUCCESS',
34+
request:{},
35+
response:{value:14}})
3436
})
35-
3637
})

0 commit comments

Comments
 (0)

[8]ページ先頭

©2009-2025 Movatter.jp