Skip to content

Commit 16b51a3

Browse files
author
keenondrums
committed
Add type reflection support for @action
1 parent ec750d0 commit 16b51a3

File tree

6 files changed

+159
-134
lines changed

6 files changed

+159
-134
lines changed

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ Consider using it with [flux-action-class](https://github.com/keenondrums/flux-a
2020
- [Old school: JavaScript](#old-school-javascript)
2121
- [Integration with `immer`](#integration-with-immer)
2222
- [In depth](#in-depth)
23-
- [When to use `@ActionReflect`](#when-to-use-actionreflect)
23+
- [When we can we omit list of actions for `@Action`?](#when-we-can-we-omit-list-of-actions-for-action)
2424
- [Running several reducers for the same action](#running-several-reducers-for-the-same-action)
2525
- [How does it compare to ngrx-actions?](#how-does-it-compare-to-ngrx-actions)
2626

@@ -68,7 +68,7 @@ Consider using it with [flux-action-class](https://github.com/keenondrums/flux-a
6868

6969
```ts
7070
import { ActionStandard } from 'flux-action-class'
71-
import { Action, ActionReflect, ReducerClass } from 'reducer-class'
71+
import { Action, ReducerClass } from 'reducer-class'
7272

7373
class ActionCatEat extends ActionStandard<number> {}
7474
class ActionCatPlay extends ActionStandard<number> {}
@@ -82,7 +82,7 @@ class ReducerCat extends ReducerClass<IReducerCatState> {
8282
energy: 100,
8383
}
8484

85-
@ActionReflect
85+
@Action
8686
addEnergy(state: IReducerCatState, action: ActionCatEat) {
8787
return {
8888
energy: state.energy + action.payload,
@@ -103,7 +103,7 @@ const reducer = ReducerCat.create()
103103
### Classic NGRX actions
104104

105105
```ts
106-
import { Action, ActionReflect, ReducerClass } from 'reducer-class'
106+
import { Action, ReducerClass } from 'reducer-class'
107107

108108
class ActionCatEat {
109109
type = 'ActionCatEat'
@@ -126,7 +126,7 @@ class ReducerCat extends ReducerClass<IReducerCatState> {
126126
energy: 100,
127127
}
128128

129-
@ActionReflect
129+
@Action
130130
addEnergy(state: IReducerCatState, action: ActionCatEat) {
131131
return {
132132
energy: state.energy + action.payload,
@@ -179,7 +179,7 @@ class ReducerCat extends ReducerClass<IReducerCatState> {
179179
const reducer = ReducerCat.create()
180180
```
181181

182-
> You might have noticed that `ActionReflect` is missing in this version. It's because we no longer use classes for our actions and TypeScript can not provide type metadata.
182+
> You might have noticed that we always pass actions to `Action` in this version. It's because we no longer use classes for our actions and TypeScript can not provide type metadata.
183183
184184
### JavaScript with flux-action-class
185185

@@ -214,7 +214,7 @@ class ReducerCat extends ReducerClass {
214214
const reducer = ReducerCat.create()
215215
```
216216

217-
> We can not use `ActionReflect` in JavaScript because there's no compiler which provides us with metadata for type reflection.
217+
> We can not use `Action` without arguments in JavaScript because there's no compiler which provides us with metadata for type reflection.
218218
219219
> Be aware, you have to configure [babel](https://babeljs.io/) to provide you with decorator syntax.
220220
@@ -262,7 +262,7 @@ Why 3? [Read pitfall #3 from immer's official documentation.](https://github.com
262262

263263
```ts
264264
import { ActionStandard } from 'flux-action-class'
265-
import { Action, ActionReflect, ReducerClass, Immutable } from 'reducer-class'
265+
import { Action, ReducerClass, Immutable } from 'reducer-class'
266266

267267
class ActionCatEat extends ActionStandard<number> {}
268268
class ActionCatPlay extends ActionStandard<number> {}
@@ -276,7 +276,7 @@ class ReducerCat extends ReducerClass<IReducerCatState> {
276276
energy: 100,
277277
}
278278

279-
@ActionReflect
279+
@Action
280280
addEnergy(state: Immutable<IReducerCatState>, draft: IReducerCatState, action: ActionCatEat) {
281281
draft.energy += action.payload
282282
}
@@ -294,17 +294,17 @@ const reducer = ReducerCat.create()
294294
295295
## In depth
296296

297-
### When to use `@ActionReflect`
297+
### When we can we omit list of actions for `@Action`?
298298

299-
You can use `@ActionReflect` if you want to run a reducer function for a single action. **Works with TypeScript only!** Action must be a class-based action. It can be a flux-action-class' action, a classic NGRX class-based action or any other class which has either a static property `type` or a property `type` on the instance of the class.
299+
You can omit list of actions for `@Action` if you want to run a reducer function for a single action. **Works with TypeScript only!** Action must be a class-based action. It can be a flux-action-class' action, a classic NGRX class-based action or any other class which has either a static property `type` or a property `type` on the instance of the class.
300300

301301
### Running several reducers for the same action
302302

303303
If you have declare several reducer functions corresponding to the same action `reducer-class` runs all of them serially (it uses its own implementation of (reduce-reducers)[https://github.com/redux-utilities/reduce-reducers]). The order is defined by [Object.keys](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys).
304304

305305
```ts
306306
import { ActionStandard } from 'flux-action-class'
307-
import { Action, ActionReflect, ReducerClass } from 'reducer-class'
307+
import { Action, ReducerClass } from 'reducer-class'
308308

309309
class ActionCatEat extends ActionStandard<number> {}
310310
class ActionCatSleep extends ActionStandard<number> {}
@@ -324,7 +324,7 @@ class ReducerCat extends ReducerClass<IReducerCatState> {
324324
}
325325
}
326326

327-
@ActionReflect
327+
@Action
328328
addMoreEnergy(state: IReducerCatState, action: ActionCatSleep) {
329329
return {
330330
energy: state.energy + action.payload * 2,
@@ -343,6 +343,6 @@ console.log(res2) // logs 135: 130 - previous value, 5 is added by addEnergy
343343
## How does it compare to [ngrx-actions](https://github.com/amcdnl/ngrx-actions)?
344344

345345
1. Stricter typings. Now you'll never forget to add initial state, return a new state from your reducer and accidentally invoke `immer` as a result and etc.
346-
1. `@ActionReflect` can be used to automatically reflect a corresponding action from the type.
346+
1. `@Action` can be used to automatically reflect a corresponding action from the type.
347347
1. `ngrx-actions` doesn't allow matching several reducers to the same action, while `reducer-class` allows you to do that and merges them for you.
348348
1. `reducer-class` is built with both worlds, Angular and Redux, in mind. It means equal support for both of them!

index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { Action, ActionReflect } from './src/decorator-action'
1+
export { Action } from './src/decorator-action'
22
export { METADATA_KEY_ACTION } from './src/constants'
33
export * from './src/errors'
44
export { ReducerClass } from './src/reducer-class'

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "reducer-class",
3-
"version": "1.0.2",
3+
"version": "1.1.0",
44
"description": "Boilerplate free class-based reducer creator. Built with TypeScript. Works with Redux and NGRX. Has integration with immer.",
55
"main": "dist/index.js",
66
"scripts": {

src/decorator-action.test.ts

Lines changed: 99 additions & 97 deletions
Original file line numberDiff line numberDiff line change
@@ -4,114 +4,116 @@ import 'reflect-metadata'
44
import { ActionStandard } from 'flux-action-class'
55

66
import { METADATA_KEY_ACTION } from './constants'
7-
import { Action, ActionReflect } from './decorator-action'
7+
import { Action } from './decorator-action'
88
import { ActionTypeUnclearError, MetadataActionPropsMissingError } from './errors'
99

1010
describe('Action', () => {
11-
test('sets metadata', () => {
12-
const prop = 'test'
13-
class Action1 extends ActionStandard {}
14-
const actionType2 = 'actionType2'
15-
class Action3 extends ActionStandard {}
16-
const action4Type = 'action4Type'
17-
class Action4 {
18-
public readonly type = action4Type
19-
constructor(public payload: any) {}
20-
}
21-
class Test {
22-
@Action(Action1, actionType2, Action3, Action4)
23-
public [prop]() {} // tslint:disable-line no-empty
24-
}
25-
const metadataExpected = [Action1.type, actionType2, Action3.type, action4Type]
26-
const metadataFromClass = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop)
27-
expect(metadataFromClass).toEqual(metadataExpected)
28-
const metadataFromInstance = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop)
29-
expect(metadataFromInstance).toEqual(metadataExpected)
30-
})
31-
test('throws if no actions passed', () => {
32-
const createFalseClass = () => {
33-
// @ts-ignore
34-
class Test {
35-
@Action()
36-
public test() {} // tslint:disable-line no-empty
11+
describe('with reflection', () => {
12+
test('sets metadata', () => {
13+
const prop1 = 'test1'
14+
const prop2 = 'test2'
15+
const prop3 = 'test3'
16+
const prop4 = 'test4'
17+
class Action1 extends ActionStandard {}
18+
const action2Type = 'action2Type'
19+
class Action2 {
20+
public readonly type = action2Type
21+
constructor(public payload: any) {}
3722
}
38-
}
39-
expect(createFalseClass).toThrow(MetadataActionPropsMissingError)
40-
})
41-
})
42-
43-
describe('ActionReflect', () => {
44-
test('sets metadata', () => {
45-
const prop1 = 'test1'
46-
const prop2 = 'test2'
47-
const prop3 = 'test3'
48-
const prop4 = 'test4'
49-
class Action1 extends ActionStandard {}
50-
const action2Type = 'action2Type'
51-
class Action2 {
52-
public readonly type = action2Type
53-
constructor(public payload: any) {}
54-
}
55-
class Test {
56-
@ActionReflect
57-
public [prop1](state: any, action: Action1) {} // tslint:disable-line no-empty
23+
class Test {
24+
@Action
25+
public [prop1](state: any, action: Action1) {} // tslint:disable-line no-empty
5826

59-
@ActionReflect
60-
public [prop2](state: any, action: Action2) {} // tslint:disable-line no-empty
27+
@Action
28+
public [prop2](state: any, action: Action2) {} // tslint:disable-line no-empty
6129

62-
@ActionReflect
63-
public [prop3](state: any, draft: any, action: Action1) {} // tslint:disable-line no-empty
30+
@Action
31+
public [prop3](state: any, draft: any, action: Action1) {} // tslint:disable-line no-empty
6432

65-
@ActionReflect
66-
public [prop4](state: any, draft: any, action: Action2) {} // tslint:disable-line no-empty
67-
}
68-
const metadataExpected1 = [Action1.type]
69-
const metadataExpected2 = [action2Type]
70-
const metadataFromClass1 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop1)
71-
expect(metadataFromClass1).toEqual(metadataExpected1)
72-
const metadataFromClass2 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop2)
73-
expect(metadataFromClass2).toEqual(metadataExpected2)
74-
const metadataFromClass3 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop3)
75-
expect(metadataFromClass3).toEqual(metadataExpected1)
76-
const metadataFromClass4 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop4)
77-
expect(metadataFromClass4).toEqual(metadataExpected2)
78-
const metadataFromInstance1 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop1)
79-
expect(metadataFromInstance1).toEqual(metadataExpected1)
80-
const metadataFromInstance2 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop2)
81-
expect(metadataFromInstance2).toEqual(metadataExpected2)
82-
const metadataFromInstance3 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop3)
83-
expect(metadataFromInstance3).toEqual(metadataExpected1)
84-
const metadataFromInstance4 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop4)
85-
expect(metadataFromInstance4).toEqual(metadataExpected2)
86-
})
87-
test('throws if no action argument provided', () => {
88-
const createFalseClass = () => {
89-
// @ts-ignore
90-
class Test {
91-
@ActionReflect
92-
public test() {} // tslint:disable-line no-empty
33+
@Action
34+
public [prop4](state: any, draft: any, action: Action2) {} // tslint:disable-line no-empty
9335
}
94-
}
95-
expect(createFalseClass).toThrow(ActionTypeUnclearError)
96-
})
97-
test('throws if too many action arguments provided', () => {
98-
const createFalseClass = () => {
99-
// @ts-ignore
100-
class Test {
101-
@ActionReflect
102-
public test(arg1: any, arg2: any, arg3: any, arg4: any) {} // tslint:disable-line no-empty
36+
const metadataExpected1 = [Action1.type]
37+
const metadataExpected2 = [action2Type]
38+
const metadataFromClass1 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop1)
39+
expect(metadataFromClass1).toEqual(metadataExpected1)
40+
const metadataFromClass2 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop2)
41+
expect(metadataFromClass2).toEqual(metadataExpected2)
42+
const metadataFromClass3 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop3)
43+
expect(metadataFromClass3).toEqual(metadataExpected1)
44+
const metadataFromClass4 = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop4)
45+
expect(metadataFromClass4).toEqual(metadataExpected2)
46+
const metadataFromInstance1 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop1)
47+
expect(metadataFromInstance1).toEqual(metadataExpected1)
48+
const metadataFromInstance2 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop2)
49+
expect(metadataFromInstance2).toEqual(metadataExpected2)
50+
const metadataFromInstance3 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop3)
51+
expect(metadataFromInstance3).toEqual(metadataExpected1)
52+
const metadataFromInstance4 = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop4)
53+
expect(metadataFromInstance4).toEqual(metadataExpected2)
54+
})
55+
test('throws if no action argument provided', () => {
56+
const createFalseClass = () => {
57+
// @ts-ignore
58+
class Test {
59+
@Action
60+
public test() {} // tslint:disable-line no-empty
61+
}
62+
}
63+
expect(createFalseClass).toThrow(ActionTypeUnclearError)
64+
})
65+
test('throws if too many action arguments provided', () => {
66+
const createFalseClass = () => {
67+
// @ts-ignore
68+
class Test {
69+
@Action
70+
public test(arg1: any, arg2: any, arg3: any, arg4: any) {} // tslint:disable-line no-empty
71+
}
72+
}
73+
expect(createFalseClass).toThrow(ActionTypeUnclearError)
74+
})
75+
test('throws if action argument type is unclear', () => {
76+
const createFalseClass = () => {
77+
// @ts-ignore
78+
class Test {
79+
@Action
80+
public test(arg1: any, arg2: undefined) {} // tslint:disable-line no-empty
81+
}
10382
}
104-
}
105-
expect(createFalseClass).toThrow(ActionTypeUnclearError)
83+
expect(createFalseClass).toThrow(ActionTypeUnclearError)
84+
})
10685
})
107-
test('throws if action argument type is unclear', () => {
108-
const createFalseClass = () => {
109-
// @ts-ignore
86+
87+
describe('without reflection', () => {
88+
test('sets metadata', () => {
89+
const prop = 'test'
90+
class Action1 extends ActionStandard {}
91+
const actionType2 = 'actionType2'
92+
class Action3 extends ActionStandard {}
93+
const action4Type = 'action4Type'
94+
class Action4 {
95+
public readonly type = action4Type
96+
constructor(public payload: any) {}
97+
}
11098
class Test {
111-
@ActionReflect
112-
public test(arg1: any, arg2: undefined) {} // tslint:disable-line no-empty
99+
@Action(Action1, actionType2, Action3, Action4)
100+
public [prop]() {} // tslint:disable-line no-empty
101+
}
102+
const metadataExpected = [Action1.type, actionType2, Action3.type, action4Type]
103+
const metadataFromClass = Reflect.getMetadata(METADATA_KEY_ACTION, Test.prototype, prop)
104+
expect(metadataFromClass).toEqual(metadataExpected)
105+
const metadataFromInstance = Reflect.getMetadata(METADATA_KEY_ACTION, new Test(), prop)
106+
expect(metadataFromInstance).toEqual(metadataExpected)
107+
})
108+
test('throws if no actions passed', () => {
109+
const createFalseClass = () => {
110+
// @ts-ignore
111+
class Test {
112+
@Action()
113+
public test() {} // tslint:disable-line no-empty
114+
}
113115
}
114-
}
115-
expect(createFalseClass).toThrow(ActionTypeUnclearError)
116+
expect(createFalseClass).toThrow(MetadataActionPropsMissingError)
117+
})
116118
})
117119
})

0 commit comments

Comments
 (0)