#VTAH
Implémenter
Redux en TypeScript
PRETEXTE
SUJET
Sara
Ounissi
level 1
Sebastien
Pittion
level 8
wideuxe
Wut?
Redux is a predictable state
container for JavaScript apps.
State
- Une sorte de BDD en mémoire
- Comprend les données de l'application
- C'est un objet dont la structure est libre
- Conseil : le plus à plat possible
Action
- Le seul moyen de muter le state
- Une action comprend un type (string)
- Et optionellement un payload (générique)
- On dispatch une action sur le store
- L'action est traitée par les reducers
- Donc le state varie en fonction des actions
Reducer
- C'est une fonction pure (sans effet de bord)
- Donc très facile à tester
- Qui prend deux paramètres : state et action
- Traite l'action donnée et retourne un nouveau state
- L'action est traitée grâce à son type
- Sinon, l'ancien state est retourné
- Note : combineReducers permet de créer
un reducer à partir d'une map de reducers
Store
- C'est l'objet qui relie state, actions et reducers
- Le store est unique
- Il expose le state courant via getState
- Il permet de dispatcher des actions
- Il appelle les reducers lors du dispatch
- On peut écouter ses changements via subscribe
- Chaque dispatch est synchrone
taïpeuscripte
Wut?
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
Installation
Configuration
{
"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"
}
}
package.json
Actions
export const enum Actions {
Init = '[Store] Init'
}
actions.ts
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 */;
}
}
redux.ts
Reducers
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]>;
};
redux.ts
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;
};
}
redux.ts
interface State {
count: number;
message: string;
}
const reducersMap: Reducers<State> = {
count, // Reducer<number>
message // Reducer<string>
};
combineReducers(reducersMap); // Reducer<State>
Store
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));
}
}
redux.ts
Exemple
Display :
count + message
Button : -1
Button : +1
Button : -10
Button : +10
DEMO !
Merci ;)
Questions ?
Implémenter Redux en TypeScript
By fingerproof
Implémenter Redux en TypeScript
https://www.meetup.com/fr-FR/VISEO-Tech-an-Hour/events/248336238/
- 1,352