Skip to content

Instantly share code, notes, and snippets.

@LucaColonnello
Last active August 7, 2020 11:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save LucaColonnello/e5a772a089c19725d90626f41628ba4a to your computer and use it in GitHub Desktop.
Save LucaColonnello/e5a772a089c19725d90626f41628ba4a to your computer and use it in GitHub Desktop.
Redux like store with per slice subscription - using bit masks to propagate changes
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<script id="jsbin-javascript">
const initialState = {
count1: 0,
count2: 0,
};
class UniqueKeysIdMap {
constructor() {
this.idCount = 0;
this.keysToId = new Map();
}
add(key) {
if (this.keysToId.has(key)) {
return;
}
this.keysToId.set(key, ++this.idCount);
}
get(key) {
return this.keysToId.get(key);
}
has(key) {
return this.keysToId.has(key);
}
delete(key) {
this.keysToId.delete(key);
}
}
class StateManager {
constructor(initialState, reducer) {
this.uniqueKeysIdMap = new UniqueKeysIdMap();
this.subscriptions = [];
this.state = initialState;
this.prevState = initialState;
this.reducer = reducer;
this.createUniqueKeysIdMap();
}
createUniqueKeysIdMap() {
Object.keys(this.state).forEach(key => {
this.uniqueKeysIdMap.add(key);
});
}
calculateChanged() {
// do it on reverse, save prevState as Set
// and remove the ones found in the first loop;
// the second loop should loop the set
// deleting the remaining ones from prevState,
// which will be the ones not found in the current state
const stateSet = new Set(Object.keys(this.state));
let changed = 0;
Object.keys(this.state).forEach(key => {
if (this.state[key] !== this.prevState[key]) {
if (!this.uniqueKeysIdMap.has(key)) {
this.uniqueKeysIdMap.add(key);
}
changed |= this.uniqueKeysIdMap.get(key);
}
});
Object.keys(this.prevState)
.filter(key => !stateSet.has(key))
.forEach(key => {
this.uniqueKeysIdMap.delete(key);
});
return changed;
}
dispatch(action) {
this.prevState = this.state;
this.state = this.reducer(this.state, action);
const changed = this.calculateChanged();
this.subscriptions.filter(
({ handler, slices }) =>
Boolean(slices.find(slice => (
this.uniqueKeysIdMap.has(slice) &&
(this.uniqueKeysIdMap.get(slice) & changed)
)))
)
.forEach(({ handler }) => {
handler(this.state);
});
}
subscribe(handler, slices) {
const subscription = {
slices,
handler
};
this.subscriptions.push(subscription);
return this.unsubscribe.bind(this, subscription);
}
unsubscribe(subscription) {
this.subscriptions.splice(this.subscriptions.findIndex(subscription), 1);
}
}
const reducer = (state, { payload: { whichOnes }, type }) => {
switch(type) {
case 'INCREMENT':
return {
...state,
count1: whichOnes.indexOf('count1') !== -1 ? state.count1 + 1 : state.count1,
count2: whichOnes.indexOf('count2') !== -1 ? state.count2 + 1 : state.count2,
};
case 'DECREMENT':
return {
...state,
count1:(whichOnes.indexOf('count1') !== -1 && state.count1 !== 0) ? state.count1 - 1 : state.count1,
count2:(whichOnes.indexOf('count2') !== -1 && state.count2 !== 0) ? state.count2 - 1 : state.count2,
};
}
};
const state = new StateManager(initialState, reducer);
state.subscribe((state) => {
console.log('called count1', state.count1);
}, ['count1']);
state.subscribe((state) => {
console.log('called count2', state.count2);
}, ['count2']);
state.subscribe((state) => {
console.log('called both', state.count1, state.count2);
}, ['count1', 'count2']);
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count1']
}
});
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count2']
}
});
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count1', 'count2']
}
});
</script>
<script id="jsbin-source-javascript" type="text/javascript">const initialState = {
count1: 0,
count2: 0,
};
class UniqueKeysIdMap {
constructor() {
this.idCount = 0;
this.keysToId = new Map();
}
add(key) {
if (this.keysToId.has(key)) {
return;
}
this.keysToId.set(key, ++this.idCount);
}
get(key) {
return this.keysToId.get(key);
}
has(key) {
return this.keysToId.has(key);
}
delete(key) {
this.keysToId.delete(key);
}
}
class StateManager {
constructor(initialState, reducer) {
this.uniqueKeysIdMap = new UniqueKeysIdMap();
this.subscriptions = [];
this.state = initialState;
this.prevState = initialState;
this.reducer = reducer;
this.createUniqueKeysIdMap();
}
createUniqueKeysIdMap() {
Object.keys(this.state).forEach(key => {
this.uniqueKeysIdMap.add(key);
});
}
calculateChanged() {
// do it on reverse, save prevState as Set
// and remove the ones found in the first loop;
// the second loop should loop the set
// deleting the remaining ones from prevState,
// which will be the ones not found in the current state
const stateSet = new Set(Object.keys(this.state));
let changed = 0;
Object.keys(this.state).forEach(key => {
if (this.state[key] !== this.prevState[key]) {
if (!this.uniqueKeysIdMap.has(key)) {
this.uniqueKeysIdMap.add(key);
}
changed |= this.uniqueKeysIdMap.get(key);
}
});
Object.keys(this.prevState)
.filter(key => !stateSet.has(key))
.forEach(key => {
this.uniqueKeysIdMap.delete(key);
});
return changed;
}
dispatch(action) {
this.prevState = this.state;
this.state = this.reducer(this.state, action);
const changed = this.calculateChanged();
this.subscriptions.filter(
({ handler, slices }) =>
Boolean(slices.find(slice => (
this.uniqueKeysIdMap.has(slice) &&
(this.uniqueKeysIdMap.get(slice) & changed)
)))
)
.forEach(({ handler }) => {
handler(this.state);
});
}
subscribe(handler, slices) {
const subscription = {
slices,
handler
};
this.subscriptions.push(subscription);
return this.unsubscribe.bind(this, subscription);
}
unsubscribe(subscription) {
this.subscriptions.splice(this.subscriptions.findIndex(subscription), 1);
}
}
const reducer = (state, { payload: { whichOnes }, type }) => {
switch(type) {
case 'INCREMENT':
return {
...state,
count1: whichOnes.indexOf('count1') !== -1 ? state.count1 + 1 : state.count1,
count2: whichOnes.indexOf('count2') !== -1 ? state.count2 + 1 : state.count2,
};
case 'DECREMENT':
return {
...state,
count1:(whichOnes.indexOf('count1') !== -1 && state.count1 !== 0) ? state.count1 - 1 : state.count1,
count2:(whichOnes.indexOf('count2') !== -1 && state.count2 !== 0) ? state.count2 - 1 : state.count2,
};
}
};
const state = new StateManager(initialState, reducer);
state.subscribe((state) => {
console.log('called count1', state.count1);
}, ['count1']);
state.subscribe((state) => {
console.log('called count2', state.count2);
}, ['count2']);
state.subscribe((state) => {
console.log('called both', state.count1, state.count2);
}, ['count1', 'count2']);
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count1']
}
});
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count2']
}
});
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count1', 'count2']
}
});
</script></body>
</html>
const initialState = {
count1: 0,
count2: 0,
};
class UniqueKeysIdMap {
constructor() {
this.idCount = 0;
this.keysToId = new Map();
}
add(key) {
if (this.keysToId.has(key)) {
return;
}
this.keysToId.set(key, ++this.idCount);
}
get(key) {
return this.keysToId.get(key);
}
has(key) {
return this.keysToId.has(key);
}
delete(key) {
this.keysToId.delete(key);
}
}
class StateManager {
constructor(initialState, reducer) {
this.uniqueKeysIdMap = new UniqueKeysIdMap();
this.subscriptions = [];
this.state = initialState;
this.prevState = initialState;
this.reducer = reducer;
this.createUniqueKeysIdMap();
}
createUniqueKeysIdMap() {
Object.keys(this.state).forEach(key => {
this.uniqueKeysIdMap.add(key);
});
}
calculateChanged() {
// do it on reverse, save prevState as Set
// and remove the ones found in the first loop;
// the second loop should loop the set
// deleting the remaining ones from prevState,
// which will be the ones not found in the current state
const stateSet = new Set(Object.keys(this.state));
let changed = 0;
Object.keys(this.state).forEach(key => {
if (this.state[key] !== this.prevState[key]) {
if (!this.uniqueKeysIdMap.has(key)) {
this.uniqueKeysIdMap.add(key);
}
changed |= this.uniqueKeysIdMap.get(key);
}
});
Object.keys(this.prevState)
.filter(key => !stateSet.has(key))
.forEach(key => {
this.uniqueKeysIdMap.delete(key);
});
return changed;
}
dispatch(action) {
this.prevState = this.state;
this.state = this.reducer(this.state, action);
const changed = this.calculateChanged();
this.subscriptions.filter(
({ handler, slices }) =>
Boolean(slices.find(slice => (
this.uniqueKeysIdMap.has(slice) &&
(this.uniqueKeysIdMap.get(slice) & changed)
)))
)
.forEach(({ handler }) => {
handler(this.state);
});
}
subscribe(handler, slices) {
const subscription = {
slices,
handler
};
this.subscriptions.push(subscription);
return this.unsubscribe.bind(this, subscription);
}
unsubscribe(subscription) {
this.subscriptions.splice(this.subscriptions.findIndex(subscription), 1);
}
}
const reducer = (state, { payload: { whichOnes }, type }) => {
switch(type) {
case 'INCREMENT':
return {
...state,
count1: whichOnes.indexOf('count1') !== -1 ? state.count1 + 1 : state.count1,
count2: whichOnes.indexOf('count2') !== -1 ? state.count2 + 1 : state.count2,
};
case 'DECREMENT':
return {
...state,
count1:(whichOnes.indexOf('count1') !== -1 && state.count1 !== 0) ? state.count1 - 1 : state.count1,
count2:(whichOnes.indexOf('count2') !== -1 && state.count2 !== 0) ? state.count2 - 1 : state.count2,
};
}
};
const state = new StateManager(initialState, reducer);
state.subscribe((state) => {
console.log('called count1', state.count1);
}, ['count1']);
state.subscribe((state) => {
console.log('called count2', state.count2);
}, ['count2']);
state.subscribe((state) => {
console.log('called both', state.count1, state.count2);
}, ['count1', 'count2']);
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count1']
}
});
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count2']
}
});
state.dispatch({
type: 'INCREMENT',
payload: {
whichOnes: ['count1', 'count2']
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment