PRETEXTE
SUJET
level 1
level 8
Redux is a predictable state
container for JavaScript apps.
TypeScript is a typed superset
of JavaScript that compiles
to plain JavaScript.
// tsconfig.json
{
"compilerOptions": {
"target": "es2015",
"module": "es2015",
"outDir": "./www/",
"rootDir": "./src/",
"strict": true,
"strictNullChecks": false,
"strictPropertyInitialization": false,
"esModuleInterop": true
}
}
npm init
npm install typescript --save-dev
./node_modules/.bin/tsc --init
{
"private": true,
"name": "ts-redux",
"version": "1.0.0",
"description": "TypeScript Redux implementation",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "concurrently -k -n watch,serve \"npm run watch\" \"npm run serve\"",
"serve": "lite-server",
"watch": "tsc --watch"
},
"author": "SPI3300",
"license": "MIT",
"devDependencies": {
"concurrently": "3.5.1",
"lite-server": "2.3.0",
"typescript": "2.7.2"
}
}
export const enum Actions {
Init = '[Store] Init'
}
import { Actions } from './actions.js';
export abstract class Action<T> {
abstract readonly type: Actions;
constructor(readonly payload: T = null) {}
}
class Init extends Action<void> {
readonly type: Actions = Actions.Init;
}
export class Action {
constructor(payload = null) {
this.payload = payload;
}
}
class Init extends Action {
constructor() {
super(...arguments);
this.type = "[Store] Init" /* Init */;
}
}
export type State = {
[key: string]: any;
};
export type Reducer<T, U extends Action<any> = Action<any>> = {
(state: T, action: U): T;
};
export type Reducers<T extends State> = {
[K in keyof T]: Reducer<T[K]>;
};
export function combineReducers<T extends State>(reducers: Reducers<T>): Reducer<T> {
// Use `Object.create(null)` to avoid potential prototypal issues.
return (oldState: T = Object.create(null), action): T => {
const newState: T = Object.create(null);
// Let only changes through to know whether to return new or old state.
return Object.keys(reducers).filter(key => {
const oldValue: any = oldState[key];
const newValue: any = reducers[key](oldValue, action);
// Store the new value in the new state.
newState[key] = newValue;
// Keep only changes in the array.
return oldValue !== newValue
// If no changes, return the old state for performance reasons.
}).length ? newState : oldState;
};
}
export function combineReducers(reducers) {
// Use `Object.create(null)` to avoid potential prototypal issues.
return (oldState = Object.create(null), action) => {
const newState = Object.create(null);
// Let only changes through to know whether to return new or old state.
return Object.keys(reducers).filter(key => {
const oldValue = oldState[key];
const newValue = reducers[key](oldValue, action);
// Store the new value in the new state.
newState[key] = newValue;
// Keep only changes in the array.
return oldValue !== newValue;
// If no changes, return the old state for performance reasons.
}).length ? newState : oldState;
};
}
interface State {
count: number;
message: string;
}
const reducersMap: Reducers<State> = {
count, // Reducer<number>
message // Reducer<string>
};
combineReducers(reducersMap); // Reducer<State>
export class Store<T extends State> {
private state: T;
private readonly emitter: DocumentFragment;
private readonly event: string = 'dispatch';
constructor(private readonly reducer: Reducer<T>) {
// Easy way to handle event listeners.
this.emitter = document.createDocumentFragment();
this.dispatch(new Init());
}
getState(): T {
return this.state;
}
subscribe(callback: Function): Function {
// Wrap callback to avoid it to return false or get access to arguments.
function handler(): void { callback(); }
this.emitter.addEventListener(this.event, handler);
return () => { this.emitter.removeEventListener(this.event, handler); };
}
dispatch<U extends Action<any>>(action: U): void {
this.state = this.reducer(this.state, action);
this.emitter.dispatchEvent(new Event(this.event));
}
}
export class Store {
constructor(reducer) {
this.reducer = reducer;
this.event = 'dispatch';
// Easy way to handle event listeners.
this.emitter = document.createDocumentFragment();
this.dispatch(new Init());
}
getState() {
return this.state;
}
subscribe(callback) {
// Wrap callback to avoid it to return false or get access to arguments.
function handler() { callback(); }
this.emitter.addEventListener(this.event, handler);
return () => { this.emitter.removeEventListener(this.event, handler); };
}
dispatch(action) {
this.state = this.reducer(this.state, action);
this.emitter.dispatchEvent(new Event(this.event));
}
}
Display :
count + message
Button : -1
Button : +1
Button : -10
Button : +10