Skip to content

Commit cb1f51f

Browse files
author
Sascha Braun
committed
ADD vuex getters to module + UPDATE readme
1 parent ad45b82 commit cb1f51f

File tree

7 files changed

+96
-15
lines changed

7 files changed

+96
-15
lines changed

README.md

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,20 @@ In this part, we create a basic counter module for vuex
3838
```ts
3939
// modules/counter.ts
4040

41-
import { Mutation, Module } from 'vuex-simple';
41+
import { Mutation, Module, Getter, State } from 'vuex-simple';
4242

4343
@Module('counter')
4444
class MyCounter {
4545
@State()
4646
public counter: number = 0;
4747

48+
// A simple getter that is cached and made reactive by vuex
49+
@Getter()
50+
public get myGetter() {
51+
console.log('my getter!');
52+
return 42;
53+
}
54+
4855
// A simple mutation function
4956
// You can only modify your state in here, or Vuex will give you an error
5057
@Mutation()
@@ -87,6 +94,9 @@ myCounter.increment();
8794
myCounter.incrementBy(10);
8895

8996
myCounter.asyncIncrement();
97+
98+
// call vuex getter
99+
myCounter.myGetter;
90100
```
91101

92102
Let's explain what we are doing here a bit:
@@ -95,6 +105,8 @@ Let's explain what we are doing here a bit:
95105

96106
- To add a mutation, we simply write a normal function and add a `@Mutation()` decorator to it. For now, mutations can only have at most 1 parameter.
97107

108+
- To add a getter, we simply write a normal getter and add a `@Getter()` decorator to it.
109+
98110
- To add an action, we simply write a normal function and add a `@Action()` decorator to it. As for mutations, actions can, for now, only have at most 1 parameter.
99111

100112
**Note on actions:** vuex use actions to do their async stuff, but we don't really need an action for that, a simple function that can call our mutations is all we need, as shown above.
@@ -131,7 +143,7 @@ Here a simple example:
131143
import { Container, Service } from 'typedi';
132144
import Vue from 'vue';
133145

134-
import VuexSimple, { getStoreBuilder, Mutation, VuexModule } from 'vuex-simple';
146+
import { Mutation, Module, State } from 'vuex-simple';
135147

136148
@Service()
137149
@Module('counter')

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vuex-simple",
3-
"version": "0.2.2",
3+
"version": "0.2.3",
44
"description": "A simpler way to write your Vuex store in Typescript",
55
"main": "build/main/index.js",
66
"typings": "build/main/index.d.ts",

src/decorators.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,12 @@ export function State<T>() {
3737
};
3838
}
3939

40+
export function Getter() {
41+
return (target: any, propertyName: string, descriptor: PropertyDescriptor) => {
42+
Utils.setDecorator(target, propertyName, DecoratorType.GETTER);
43+
};
44+
}
45+
4046
export function Mutation<T>() {
4147
return (
4248
target: T,

src/metadata.ts

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import Vue from 'vue';
2-
import { Action as Act, Mutation as Mut } from 'vuex';
2+
import { Action as Act, Getter, Mutation as Mut } from 'vuex';
33

44
import { ModuleBuilder } from './module-builder';
55
import { DecoratorType } from './types';
@@ -21,6 +21,7 @@ export function applyDecorators<State>(moduleBuilder: ModuleBuilder<State, any>,
2121
applyStates(instance);
2222

2323
const properties = Object.getOwnPropertyNames(proto);
24+
2425
properties.forEach(key => {
2526
if (key === 'constructor') {
2627
return;
@@ -31,6 +32,12 @@ export function applyDecorators<State>(moduleBuilder: ModuleBuilder<State, any>,
3132

3233
if (decorator) {
3334
switch (decorator) {
35+
case DecoratorType.GETTER:
36+
const accessorDescriptor = Object.getOwnPropertyDescriptor(proto, key);
37+
if (accessorDescriptor && typeof accessorDescriptor.get === 'function') {
38+
applyGetter(moduleBuilder, instance, constructor, accessorDescriptor, key);
39+
}
40+
break;
3441
case DecoratorType.MUTATION:
3542
if (descriptor) {
3643
applyMutation<State>(moduleBuilder, instance, constructor, descriptor, key);
@@ -86,6 +93,27 @@ function applyMutation<State>(
8693
};
8794
}
8895

96+
function applyGetter<State>(
97+
moduleBuilder: ModuleBuilder<State, any>,
98+
instance: any,
99+
constructor: any,
100+
descriptor: PropertyDescriptor,
101+
propertyName: string
102+
) {
103+
let getterFunction: Function = descriptor.get ? descriptor.get : (state: any) => ({});
104+
getterFunction = getterFunction.bind(instance);
105+
const getter: Getter<State, any> = state => {
106+
return getterFunction();
107+
};
108+
moduleBuilder.addGetter(propertyName, getter);
109+
110+
Object.defineProperty(constructor.prototype, propertyName, {
111+
get() {
112+
return moduleBuilder.read(propertyName);
113+
}
114+
});
115+
}
116+
89117
function applyAction<State>(
90118
moduleBuilder: ModuleBuilder<State, any>,
91119
instance: any,

src/module-builder.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Action, Module, Mutation } from 'vuex';
1+
import { Action, Getter, Module, Mutation } from 'vuex';
22

33
import { StoreBuilder } from './store-builder';
44

@@ -7,16 +7,13 @@ export class ModuleBuilder<State = any, RootState = any> {
77
private _module: Module<State, RootState>;
88
private _namespace: string;
99

10-
constructor(
11-
storeBuilder: StoreBuilder<RootState>,
12-
namespace: string,
13-
state: State
14-
) {
10+
constructor(storeBuilder: StoreBuilder<RootState>, namespace: string, state: State) {
1511
this._storeBuilder = storeBuilder;
1612
this._namespace = namespace;
1713
this._module = {
1814
actions: {},
1915
mutations: {},
16+
getters: {},
2017
namespaced: true,
2118
state
2219
};
@@ -32,18 +29,31 @@ export class ModuleBuilder<State = any, RootState = any> {
3229
this._module.mutations[name] = mutation;
3330
}
3431
}
32+
public addGetter(name: string, getter: Getter<State, RootState>) {
33+
if (this._module.getters) {
34+
this._module.getters[name] = getter;
35+
}
36+
}
3537

3638
public commit(name: string, payload: any) {
3739
if (this._storeBuilder && this._storeBuilder.store) {
38-
this._storeBuilder.store.commit(this.namespace + '/' + name, payload);
40+
return this._storeBuilder.store.commit(this.namespace + '/' + name, payload);
41+
} else {
42+
throw new Error('Could not commit: no store created');
43+
}
44+
}
45+
46+
public read(name: string) {
47+
if (this._storeBuilder && this._storeBuilder.store) {
48+
return this._storeBuilder.store.getters[this.namespace + '/' + name];
3949
} else {
4050
throw new Error('Could not commit: no store created');
4151
}
4252
}
4353

4454
public dispatch(name: string, payload: any) {
4555
if (this._storeBuilder && this._storeBuilder.store) {
46-
this._storeBuilder.store.dispatch(this.namespace + '/' + name, payload);
56+
return this._storeBuilder.store.dispatch(this.namespace + '/' + name, payload);
4757
} else {
4858
throw new Error('Could not dispatch: no store created');
4959
}

src/tests/simple.spec.ts

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,27 @@
11
import untypedTest, { TestInterface } from 'ava';
22
import Vue from 'vue';
33

4+
import { Getter } from '../decorators';
45
import VuexSimple, { getStoreBuilder, Module, Mutation, State } from '../index';
56

67
@Module('test')
78
class TestModule {
89
@State()
910
public counter: number = 0;
1011

12+
@Getter()
13+
public get cachedGetter() {
14+
return {
15+
item: this.counter + 100
16+
};
17+
}
18+
19+
public get normalGetter() {
20+
return {
21+
item: this.counter + 100
22+
};
23+
}
24+
1125
@Mutation()
1226
public increment() {
1327
this.counter++;
@@ -39,13 +53,23 @@ test('simple increment', t => {
3953
t.is(t.context.testModule.counter, 1);
4054
});
4155

56+
test('simple computed getters', t => {
57+
t.not(t.context.testModule.normalGetter, t.context.testModule.normalGetter);
58+
t.is(t.context.testModule.cachedGetter, t.context.testModule.cachedGetter);
59+
t.is(t.context.testModule.cachedGetter.item, 101);
60+
61+
t.notThrows(() => t.context.testModule.increment());
62+
63+
t.is(t.context.testModule.cachedGetter.item, 102);
64+
});
65+
4266
test('simple incrementAsync', async t => {
43-
t.is(t.context.testModule.counter, 1);
67+
t.is(t.context.testModule.counter, 2);
4468

4569
const p = t.context.testModule.asyncIncrement();
4670
t.notThrowsAsync(() => p);
4771

4872
await p;
4973

50-
t.is(t.context.testModule.counter, 2);
74+
t.is(t.context.testModule.counter, 3);
5175
});

src/types.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
export enum DecoratorType {
22
STATE = 1,
33
ACTION,
4-
MUTATION
4+
MUTATION,
5+
GETTER
56
}
67

78
export type DecoratorMap = Map<string, DecoratorType>;

0 commit comments

Comments
 (0)