|
import { createStore, combineReducers, applyMiddleware } from 'redux'; |
|
|
|
// Actions |
|
const RECEIVE_UPDATE = 'RECEIVE_UPDATE'; |
|
function receiveUpdate(counter) { |
|
return { |
|
type: RECEIVE_UPDATE, |
|
payload: { |
|
counter |
|
} |
|
}; |
|
} |
|
|
|
const UNDO = 'UNDO'; |
|
function undo() { |
|
return { |
|
type: UNDO |
|
} |
|
} |
|
|
|
function add(value) { |
|
return (dispatch, getState) => { |
|
const { counter } = getState(); |
|
const newValue = counter + value; |
|
|
|
return new Promise((resolve, reject) => { |
|
resolve(newValue); |
|
}).then((data) => { |
|
dispatch(receiveUpdate(data)); |
|
}); |
|
} |
|
} |
|
|
|
function sub(value) { |
|
return (dispatch, getState) => { |
|
const { counter } = getState(); |
|
const newValue = counter - value; |
|
|
|
return new Promise((resolve, reject) => { |
|
resolve(newValue); |
|
}).then((data) => { |
|
dispatch(receiveUpdate(data)); |
|
}); |
|
} |
|
} |
|
|
|
function mul(value) { |
|
return (dispatch, getState) => { |
|
const { counter } = getState(); |
|
const newValue = counter * value; |
|
|
|
return new Promise((resolve, reject) => { |
|
resolve(newValue); |
|
}).then((data) => { |
|
dispatch(receiveUpdate(data)); |
|
}); |
|
} |
|
} |
|
|
|
function div(value) { |
|
return (dispatch, getState) => { |
|
const { counter } = getState(); |
|
const newValue = counter / value; |
|
|
|
return new Promise((resolve, reject) => { |
|
resolve(newValue); |
|
}).then((data) => { |
|
dispatch(receiveUpdate(data)); |
|
}); |
|
} |
|
} |
|
|
|
// Commands |
|
class Command { |
|
execute() { |
|
throw new Error('Not Implemented'); |
|
} |
|
|
|
undo() { |
|
throw new Error('Not Implemented'); |
|
} |
|
} |
|
|
|
class AddCommand extends Command { |
|
constructor(value) { |
|
super(); |
|
this.value = value; |
|
} |
|
|
|
execute() { |
|
return add(this.value); |
|
} |
|
|
|
undo() { |
|
return sub(this.value); |
|
} |
|
} |
|
|
|
class SubCommand extends Command { |
|
constructor(value) { |
|
super(); |
|
this.value = value; |
|
} |
|
|
|
execute() { |
|
return sub(this.value); |
|
} |
|
|
|
undo() { |
|
return add(this.value); |
|
} |
|
} |
|
|
|
class MulCommand extends Command { |
|
constructor(value) { |
|
super(); |
|
this.value = value; |
|
} |
|
|
|
execute() { |
|
return mul(this.value); |
|
} |
|
|
|
undo() { |
|
return div(this.value); |
|
} |
|
} |
|
|
|
class DivCommand extends Command { |
|
constructor(value) { |
|
super(); |
|
this.value = value; |
|
} |
|
|
|
execute() { |
|
return div(this.value); |
|
} |
|
|
|
undo() { |
|
return mul(this.value); |
|
} |
|
} |
|
|
|
|
|
// Middleware |
|
let commands = []; |
|
function undoMiddleware({ dispatch, getState }) { |
|
return function (next) { |
|
return function (action) { |
|
if (action instanceof Command) { |
|
// Call the command |
|
const promise = action.execute(action.value); |
|
commands.push(action); |
|
return promise(dispatch, getState); |
|
} else { |
|
if (action.type === UNDO) { |
|
const command = commands.pop(); |
|
const promise = command.undo(command.value); |
|
return promise(dispatch, getState); |
|
} else { |
|
return next(action); |
|
} |
|
} |
|
}; |
|
}; |
|
} |
|
|
|
// Reducer |
|
function counterReducer(state=0, action) { |
|
switch (action.type) { |
|
case RECEIVE_UPDATE: |
|
return action.payload.counter; |
|
default: |
|
return state; |
|
} |
|
} |
|
|
|
const appReducer = combineReducers({ |
|
counter: counterReducer |
|
}); |
|
|
|
|
|
// Store |
|
const createStoreWithMiddleware = applyMiddleware( |
|
undoMiddleware, |
|
)(createStore); |
|
|
|
|
|
// App |
|
const store = createStoreWithMiddleware(appReducer); |
|
|
|
store.subscribe(() => { |
|
let state = store.getState(); |
|
console.log('-----------------------------------'); |
|
console.log('state', state); |
|
console.log('commands', commands); |
|
console.log(''); |
|
}); |
|
|
|
store.dispatch(new AddCommand(10)) |
|
.then(() => store.dispatch(new SubCommand(2))) |
|
.then(() => store.dispatch(new AddCommand(5))) |
|
.then(() => store.dispatch(undo())) |
|
.then(() => store.dispatch(undo())) |
|
.then(() => store.dispatch(new MulCommand(4))) |
|
.then(() => store.dispatch(new DivCommand(2))) |
|
.then(() => store.dispatch(undo())) |