Skip to content

Instantly share code, notes, and snippets.

@rpominov
Created May 12, 2015 09:52
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rpominov/9e09a7c1228e813a40ef to your computer and use it in GitHub Desktop.
Save rpominov/9e09a7c1228e813a40ef to your computer and use it in GitHub Desktop.
Another Flux
const flux = createFlux();
flux.addStore('counter', {
initial() {
return 0;
},
reducers: {
add(cur, x) {
return cur + x;
},
subtract(cur, x) {
return cur - x;
}
}
});
// We can create several stores listening to same events.
flux.addStore('counterReverce', {
initial() {
return 0;
},
reducers: {
add(cur, x) {
return cur - x;
},
subtract(cur, x) {
return cur + x;
}
}
});
// Action names don't have to be the same as events types they produce.
flux.addAction('plus', (flux, x) => {
flux.dispatch('add', x);
});
flux.addAction('minus', (flux, x) => {
flux.dispatch('subtract', x);
});
const flux = createFlux();
flux.addStore('scrollTop', {
initial() {
return null;
},
reducers: {
updateScrollTop(_, next) {
return next;
}
}
});
flux.addAction('watchScroll', (flux) => {
const update = () => {
flux.dispatch('updateScrollTop', document.body.scrollTop);
}
update();
document.addEventListener('scroll', update);
flux.subscribeToDispose(() => document.removeEventListener('scroll', update));
});
flux.subscribe('scrollTop', (prev) => console.log('Scroll updated ', prev, '->', flux.state.scrollTop));
flux.actions.watchScroll();
// later...
flux.dispose();
flux = null;
const flux = createFlux();
flux.addStore('todos', {
initial() {
return [];
},
// Reducers must be pure functions! The method names are event types,
// they are share single namespace per flux instance.
reducers: {
todoAdd(items, text) {
return items.concat([ {text, completed: false, id: createId()} ]);
},
todoRemove(items, id) {
return items.filter(item => item.id !== id);
},
todoUpdateText(items, {id, text}) {
return items.map(item => item.id === id ? {id, text, completed: item.completed} : item);
},
todoComplete(items, id) {
return items.map(item => item.id === id ? {id, text: item.text, completed: true} : item);
}
}
});
// Same name for the action and the event it produces is coincidental.
flux.addAction('todoAdd', (flux, text) => {
// In an action you are allowed to read `flux.state`, but should avoid that.
// In an action you can dispatch any number of events,
// synchronously or asynchronously.
flux.dispatch('todoAdd', text);
});
console.log("Initial 'todos' state", flux.state.todos); // []
const unsubscribe = flux.subscribe('todos', (prevState) => {
console.log("State of 'todos' have changed from", prevState, "to", flux.state.todos);
});
flux.actions.todoAdd('Implement Flux');
// > State of 'todos' have changed from [] to [{"text":"Implement Flux","completed":false,"id":42}]
// We should have create an action for this...
flux.dispatch('todoComplete', 42);
// > State of 'todos' have changed from [{"text":"Implement Flux","completed":false,"id":42}] to
// ... [{"id":42,"text":"Implement Flux","completed":true}]
// Supposed to return unique IDs
function createId() {
return 42;
}
function createFlux() {
let reducers = {};
let currentlyDispatching = null;
let onChange = {};
let onDispose = [];
let flux = {
state: {},
actions: {},
addStore(name, store) {
flux.state[name] = store.initial();
Object.keys(store.reducers).forEach(type => {
reducers[type] = reducers[type] || [];
reducers[type].push({name, reducers: store.reducers});
});
},
addAction(name, fn) {
flux.actions[name] = (...args) => {
return fn(flux, ...args);
};
},
dispatch(type, payload) {
if (currentlyDispatching === null) {
currentlyDispatching = type;
if (reducers[type]) {
reducers[type].forEach(({name, reducers}) => {
const prevState = flux.state[name];
flux.state[name] = reducers[type](prevState, payload);
if (flux.state[name] !== prevState && onChange[name]) {
onChange[name].forEach(callback => callback(prevState));
}
});
}
currentlyDispatching = null;
} else {
throw new Error(`Can't dispatch event '${type}' because currently dispatching another event '${currentlyDispatching}'`);
}
},
subscribe(name, callback) {
onChange[name] = onChange[name] ? onChange[name].concat([callback]) : [callback];
return () => {
onChange[name] = removeFirst(onChange[name], callback);
};
},
subscribeToDispose(callback) {
onDispose = onDispose.concat(callback);
return () => {
onDispose = removeFirst(onDispose, callback);
};
},
dispose() {
onDispose.forEach(callback => callback());
flux.state = flux.actions = reducers = onChange = onDispose = null;
}
};
return flux;
}
function removeFirst(array, item) {
let met = false;
return array.filter(_item => met || _item !== item || (met = true, false));
}
@rpominov
Copy link
Author

Instead of:

flux.addAction('foo', (flux, arg1, agr2) => ...)

it probably should be:

flux.addAction('foo', (flux) => {
  // Here you might want to create some state related to the action.
  // For example `intervalId` for calling `clearInterval(intervalId)` 
  // on next action call if necessary. Or perhaps XHR object, 
  // if you want to cancel previous request when new comes.
  return (arg1, agr2) => ...;
})

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment