UNPKG

command-pack

Version:

cqs pattern for redux

211 lines (158 loc) 4.73 kB
# Command-Pack for redux CQS implementation for redux https://martinfowler.com/bliki/CommandQuerySeparation.html ## Installation ```bash npm i -S command-pack ``` ## Counter Sample ### Sample for Counter First I need to explain what is `command` and `handler`. Basically a command is just a data dictionary that carries required parameters for the `handler`. To create a command it is needed to make a new brand class that extends command-pack `Command`. #### Commands: For our counter sample, we need two commands: First, IncreaseCounter to increase total and DecreaseCounter. **- IncreaseCounter.js** ```js import {Command} from "command-pack"; export default class IncreaseCounter extends Command { amount; constructor(amount) { super(); this.amount = amount; } handle(state) { return { ...state, total: state.total + this.amount }; } } ``` **- DecreaseCounter.js** ```js import {Command} from "command-pack"; export default class IncreaseCounter extends Command { amount; constructor(amount) { super(); this.amount = amount; } handle(state) { return { ...state, total: state.total - this.amount }; } } ``` Now we have commands... and they know how to handle this data parameters with handle method by given `state`. ### Command Registration ```js import { CommandContainer } from "command-pack"; const container = new CommandContainer("counterStore") // Store name .setDefaultState({total: 0}) // Store default state .register(IncreaseCounter) // our commands .register(DecreaseCounter); ``` ### CommandExecutor ```js import { CommandContainer, CommandExecutor} from "command-pack"; CommandExecutor.add(new CommandContainer("counterStore") .setDefaultState({total: 0}) .register(IncreaseCounter) .register(DecreaseCounter) ); ``` ### CommandExecutor.createStore() for Redux-Provider ```js import React from 'react'; import ReactDOM from 'react-dom'; import Provider from "react-redux/src/components/Provider"; import { CommandContainer, CommandExecutor} from "command-pack"; import IncreaseCounter from "./components/counter/IncreaseCounter"; import DecreaseCounter from "./components/counter/DecreaseCounter"; import Counter from './components/counter/Counter'; CommandExecutor.add(new CommandContainer("counterStore") .setDefaultState({total: 0}) .register(IncreaseCounter) .register(DecreaseCounter) ); ReactDOM .render( <Provider store={CommandExecutor.createStore()}> <div> <h2>COUNTER Sample</h2> <Counter/> </div> </Provider> , document.getElementById('root')); registerServiceWorker(); ``` ### Counter React Component ```js import React from 'react' import {connect} from "react-redux"; import {CommandExecutor} from "command-pack"; import DecreaseCounter from "./DecreaseCounter"; import IncreaseCounter from "./IncreaseCounter"; class Counter extends React.Component { inc() { CommandExecutor.execute(new IncreaseCounter(1)); } dec() { CommandExecutor.execute(new DecreaseCounter(1)); } render() { const total = this.props.counterStore.total; return (<div>Counter : {total} <button onClick={this.dec.bind(this)}>decrease</button> <button onClick={this.inc.bind(this)}>increase</button> </div>) } } export default connect(x => { return {counterStore: x.counterStore} })(Counter); ``` ### ASYNC example To solve that problem, I tried with middlewares thunk and etc... But it just increases complexity. With command-pack it is really easy to implement an async flow. Basically you need two commands: - `StartDownload` to start flow - `DownloadCompleted` to get the results **- StartDownload.js** ```js import {Command, CommandExecutor} from "command-pack"; export default class StartDownload extends Command { url; constructor(url) { super(); this.url = url; } handle(state) { fetch (this.url).then (content=>{ CommandExecutor.execute (new DownloadCompleted (content)); }); return { ...state, downloading : true }; } } ``` **- DownloadCompleted.js** ```js import {Command} from "command-pack"; export default class DownloadCompleted extends Command { content; constructor(content) { super(); this.content = content; } handle(state) { return { ...state, downloading : false, content : this.content }; } }