UNPKG

redux-typed-kit

Version:

Powerful extensitions for building class-based Redux architecture powered by TypeScript.

244 lines (187 loc) 7.53 kB
# redux-typed-kit Powerful extensitions for building class-based Redux architecture powered by TypeScript. Example of using [redux-typed-kit-example](https://github.com/ordenilluminaten/redux-typed-kit-example) <!-- START doctoc generated TOC please keep comment here to allow auto update --> <!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --> - [Installation](#installation) - [Quick start](#quick-start) - [States](#states) - [RebuildState](#rebuildState) - [Actions](#actions) - [Reducers](#reducers) - [CombinedReducer](#combinedReducer) - [RootReducer](#rootReducer) - [Middlewares](#middlewares) - [PreMiddlewareHandler](#preMiddlewareHandler) - [PostMiddlewareHandler](#postMiddlewareHandler) - [Middlewares inheritance](#middlewaresInheritance) - [Store](#store) - [Example](#example) - [Integration with third-party libs](#integrationWithThirdPartyLibs) <!-- END doctoc generated TOC please keep comment here to allow auto update --> ## Installation 1. Run ``` npm i redux-typed-kit ``` 1. Configure your Babel to support class properties and decorators inside `.babelrc` ```json "plugins": [ ["@babel/plugin-proposal-decorators", { "legacy": true }], ["@babel/plugin-proposal-class-properties", { "loose": true }] ] ``` 1. If you use TypeScript you should configure it inside `tsconfig.json` ```json "experimentalDecorators": true, "emitDecoratorMetadata": true, ``` 1. If you use React you also should install reflect-metadata ``` npm i reflect-metadata ``` and then import it at the top of the entry file ```ts import 'reflect-metadata'; ``` ## Quick start Let's create a simple Redux-architecture to fetch users from server and display them. We'll also take care about possible network (and others) errors. ### States Let's describe users state ```ts import { State } from 'redux-typed-kit'; export default class UsersState extends State { public users: Array<User> = []; public filter: UsersFilter = new UsersFilter(); public isLoading: boolean = false; public error: ApiError; } ``` AppState should include all other states ```ts import { State } from 'redux-typed-kit'; export default class AppState extends State { public usersState: UsersState; } ``` #### RebuildState The `State` class has the `Rebuild` method, which is needed to create an instance of the `State` class, based on the parent instance, but with some changes. It's very helpful to use in any reducers, especially when you need to save the type of your state class. ```ts state.rebuild(x => { x.isLoading = true; }); ``` ### Actions Let's create three actions for fetching users. Action to start fetching, action in case of success and in case of error. ```ts import { Action } from 'redux-typed-kit'; export class FetchUsersAction extends Action { constructor(public filter: FetchUsersFilter) { super(); } } export class FetchUsersSuccessAction extends Action { constructor(public response: FetchUsersResponse) { super(); } } export class FetchUsersFailureAction extends Action { constructor(public error: ApiError) { super(); } } ``` ### Reducers Let's create users reducer. Every reducer must have two basic things: initial state and name. This name will be used to configure the global app state. ```ts import { Reducer, ActionDecorator as Action } from 'redux-typed-kit'; import UsersState from '../models/states/users-state'; import { FetchUsersAction, FetchUsersSuccessAction, FetchUsersFailureAction } from '../actions/fetch-users-action'; export default class UsersReducer extends Reducer<UsersState> { initialState = new UsersState(); name = 'usersState'; @Action fetchUsers(state: UsersState, action: FetchUsersAction): UsersState { return state.rebuild(x => { x.isLoading = !action.isBackgroud; }); } @Action fetchUsersSuccess(state: UsersState, action: FetchUsersSuccessAction): UsersState { return state.rebuild(x => { x.users = action.response.users; x.isLoading = false; }); } @Action fetchUsersFailure(state: UsersState, action: FetchUsersFailureAction): UsersState { return state.rebuild(x => { x.error = action.error; x.isLoading = false; }); } } ``` #### CombinedReducer `CombinedReducer` combines several reducers into one complex reducer and state object just like `combineReducers` function does. ```ts import { CombinedReducer } from 'redux-typed-kit'; export default new CombinedReducer('complexState', new SimpleReducer(), new AnotherSimpleReducer()); ``` #### RootReducer `RootReducer` combines others reducers (including `CombinedReducer`) and used by store. ```ts import { RootReducer } from 'redux-typed-kit'; import UsersReducer from './users-reducer'; const rootReducer = new RootReducer( new UsersReducer() /*, new CombinedReducer(...), new AnotherReducer()*/ ); export default rootReducer; ``` ## Middlewares We will use middleware to make a request to the server, process the response, and send data to the reducers. ```ts import { PostMiddlewareHandler, Middleware, Store } from 'redux-typed-kit'; import AppState from '../models/states/app-state'; import { FetchUsersAction, FetchUsersSuccessAction, FetchUsersFailureAction, FetchUsersCancelAction } from '../actions/fetch-users-action'; export default class UsersMiddleware extends Middleware<AppState> { @PostMiddlewareHandler async fetchUsers(store: Store<AppState>, action: FetchUsersAction) { const result = await api.fetchUsers(action.filter); if (result.error == null) { store.dispatch(new FetchUsersSuccessAction(result.response)); } else { store.dispatch(new FetchUsersFailureAction(result.error)); } } } ``` ### PreMiddlewareHandler `PreMiddlewareHandler` decorator is used when we need to catch any dispached actions before they get to the reducers. So we can get state from `Store` before any changes are applied inside the reducers. ### PostMiddlewareHandler `PostMiddlewareHandler` decorator works exactly the opposite. All reducers already handled the action and changed the state. Probably the best example I can give you is middlware for logging all actions and state's changes. ```ts import { PostMiddlewareHandler, PreMiddlewareHandler, Middleware, Store } from 'redux-typed-kit'; export default class LoggerMiddleware extends Middleware<AppState> { @PreMiddlewareHandler preLog(store: Store<AppState>, action: any) { console.log('PRE', action, JSON.parse(JSON.stringify(store.getState()))); } @PostMiddlewareHandler postLog(store: Store<AppState>, action: any) { console.log('POST', action, JSON.parse(JSON.stringify(store.getState()))); } } ``` ## Store `Store` is a combination of `RootReducer` and `Middlewares` that create a final state object. ```ts import { Store } from 'vue-redux-ts'; import AppState from '../models/states/app-state'; import rootReducer from '../reducers/root-reducer'; import UsersMiddleware from '../middlewares/users-middleware'; const store = new Store<AppState>(rootReducer, new UsersMiddleware()); export default store.init(); ``` # Documentation coming soon...