-
Notifications
You must be signed in to change notification settings - Fork 182
/
simpleRedux.ts
109 lines (90 loc) · 3.55 KB
/
simpleRedux.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import {createStore} from "redux";
/**
* Instead of creating a "reducer" (with a switch statement!) + "actionCreator"
* You just call `add` to provide a typeSafe "reducer" and we give you a typeSafe "actionCreator"
*/
export class SimpleRedux<State>{
public store: Redux.Store;
private _listeners = {};
constructor(public initialState?: State) {
this.store = createStore(this._reducer);
}
add<Payload>(usefulNameForDebugging: string, reducer: (state: State, payload: Payload) => State): { (payload: Payload): void; } {
if (this._listeners[usefulNameForDebugging]) throw new Error(`REDUX: already have reducer "${usefulNameForDebugging}"`);
let dispatcher = (payload) => this.store.dispatch({
type: usefulNameForDebugging,
payload: payload
});
this._listeners[usefulNameForDebugging] = reducer;
return dispatcher;
}
/**
* WARNING: this only supports 1 level of nesting
*/
addSub<Payload, SubState>(usefulNameForDebugging: string, select: (state) => SubState, reducer: (state: SubState, payload: Payload) => SubState): { (payload: Payload): void; } {
let dispatcher = (payload) => this.store.dispatch({
type: usefulNameForDebugging,
payload: payload
});
this._listeners[usefulNameForDebugging] = (state: State, payload: Payload): State => {
let sub = select(state);
for (var key in state) {
if (state[key] == sub) break;
}
let newSub = reducer(sub, payload);
state[key] = newSub;
return state;
};
return dispatcher;
}
getState = (): State => {
return this.store.getState();
}
subscribe = (changed: { (state: State): any }): { dispose: () => any } => {
return { dispose: this.store.subscribe(() => changed(this.getState())) as any };
}
subscribeSub = <SubState>(select: (state: State) => SubState, onChange: (state: SubState) => void): { dispose: () => any } => {
let currentState = select(this.getState());
let handleChange = () => {
let nextState = select(this.getState());
if (nextState !== currentState) {
currentState = nextState;
onChange(currentState);
}
}
return this.subscribe(handleChange);
}
private _reducer = (state: State = this.initialState, action: any): State => {
if (this._listeners[action.type])
return this._extendState(state, this._listeners[action.type](state, action.payload));
else return state;
}
private _extendState(state: State, newState: State): State {
let result = {};
for (let key in state) {
result[key] = state[key];
}
for (let key in newState) {
result[key] = newState[key];
}
return result as State;
}
/**
* Take every field of fields and put them override them in the complete object
* NOTE: this API is a bit reverse of extend because of the way generic constraints work in TypeScript
*/
updateFields = <T>(fields: T) => <U extends T>(complete: U): U => {
let result = <U>{};
for (let id in complete) {
result[id] = complete[id];
}
for (let id in fields) {
result[id] = fields[id];
}
return result;
}
// Update the item at index
updateArrayItem<T>(array:T[],index:number,item:T){
return array.map((x, i) => i == index ? item : x);
}
}