Skip to content

Instantly share code, notes, and snippets.

@gaearon
Last active March 25, 2024 19:12
Show Gist options
  • Save gaearon/ffd88b0e4f00b22c3159 to your computer and use it in GitHub Desktop.
Save gaearon/ffd88b0e4f00b22c3159 to your computer and use it in GitHub Desktop.
Redux without the sanity checks in a single file. Don't use this, use normal Redux. :-)
function mapValues(obj, fn) {
return Object.keys(obj).reduce((result, key) => {
result[key] = fn(obj[key], key);
return result;
}, {});
}
function pick(obj, fn) {
return Object.keys(obj).reduce((result, key) => {
if (fn(obj[key])) {
result[key] = obj[key];
}
return result;
}, {});
}
function bindActionCreator(actionCreator, dispatch) {
return (...args) => dispatch(actionCreator(...args));
}
export function bindActionCreators(actionCreators, dispatch) {
return typeof actionCreators === 'function' ?
bindActionCreator(actionCreators, dispatch) :
mapValues(actionCreators, actionCreator =>
bindActionCreator(actionCreator, dispatch)
);
}
export function compose(...funcs) {
return arg => funcs.reduceRight((composed, f) => f(composed), arg);
}
export function applyMiddleware(...middlewares) {
return (next) => (reducer, initialState) => {
var store = next(reducer, initialState);
var dispatch = store.dispatch;
var chain = [];
chain = middlewares.map(middleware => middleware({
getState: store.getState,
dispatch: (action) => dispatch(action)
}));
dispatch = compose(...chain)(store.dispatch);
return { ...store, dispatch };
};
}
export function combineReducers(reducers) {
var finalReducers = pick(reducers, (val) => typeof val === 'function');
return (state = {}, action) => mapValues(finalReducers,
(reducer, key) => reducer(state[key], action)
);
}
export function createStore(reducer, initialState) {
var currentReducer = reducer;
var currentState = initialState;
var listeners = [];
var isDispatching = false;
function getState() {
return currentState;
}
function subscribe(listener) {
listeners.push(listener);
return function unsubscribe() {
var index = listeners.indexOf(listener);
listeners.splice(index, 1);
};
}
function dispatch(action) {
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.');
}
try {
isDispatching = true;
currentState = currentReducer(currentState, action);
} finally {
isDispatching = false;
}
listeners.slice().forEach(listener => listener());
return action;
}
function replaceReducer(nextReducer) {
currentReducer = nextReducer;
dispatch({ type: '@@redux/INIT' });
}
dispatch({ type: '@@redux/INIT' });
return { dispatch, subscribe, getState, replaceReducer };
}
@dtinth
Copy link

dtinth commented May 4, 2016

@dlwalsh It’s algorithmic complexity. The “purer” version is O(n2) where the original version is O(n), so it’s not the same as using for loops vs Array#reduce. Here’s a benchmark:

function mapValues1(obj, fn) {
  return Object.keys(obj).reduce((result, key) => {
    result[key] = fn(obj[key], key);
    return result;
  }, {});
}

function mapValues2(obj, fn) {
  return Object.keys(obj).reduce((result, key) => (
    Object.assign({}, result, {
      [key]: fn(obj[key], key)
    })
  ), {});
}

var obj = { }
for (var i = 0; i < 10000; i ++) obj[i] = i

var mapper = x => x * x

console.time('mapValues1')
var result1 = mapValues1(obj, mapper)
console.timeEnd('mapValues1')

console.time('mapValues2')
var result2 = mapValues2(obj, mapper)
console.timeEnd('mapValues2')

// Ensure result is correct :)
require('assert').equal(result1[100], 10000)
require('assert').deepEqual(result1, result2)

Result:

mapValues1: 7.764ms
mapValues2: 10896.908ms

You can see that it takes 10 seconds to map a 10000-key object if you create a new object every time. Both functions are equally pure as @CrocoDillon said, since all the mutations happen inside the function.

@davesnx
Copy link

davesnx commented May 4, 2016

Well tested @dtinth, but it's basically the Object.assign creates a new object(with result and matches the key) each iteration, and the mapValues1 just add as a key in the resultant object.

@vasanthk
Copy link

Pure Genius!

@LucaColonnello
Copy link

I did the same for my Redux Course to explain how really Redux works and what is the concept. I notice that in a lot of courses about Redux, teachers miss the importance of selectors...

@tonytangau
Copy link

Artistic

@ansonkao
Copy link

It's possible to slim it down even further by removing the isDispatching checks! Then it would be < 90 lines!

@sabha
Copy link

sabha commented Oct 24, 2016

Thanks Dan. Do we have similar one for React-Redux. Trying to understand the connect method for adding features like "Resolve data before mounting the container".

@jakl
Copy link

jakl commented Dec 13, 2016

I would love to watch a youtube video going over in detail every line of this code.

@thangchung
Copy link

Awesome. Simple but not simpler 👍

@ehzawad
Copy link

ehzawad commented Feb 24, 2017

Cool

@wayofthefuture
Copy link

Haters don't hate you know it's genius... #imnotworthy#

@ptim
Copy link

ptim commented Jun 11, 2017

@sabha you probably found it already, but for those who com after, here is connect explained: https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e

@gandhirahul
Copy link

Amazingly beautiful code 👍

@LorisBachert
Copy link

I'm heavily breathing right now. This is just pure awesomeness!

@Shawnxkwang
Copy link

It's 2018. Is it too late for me to say 'cool'?

@ryuhangyeong
Copy link

Wow!

@sag1v
Copy link

sag1v commented Mar 28, 2019

4 years later...
I'm curious why you use .slice on this line
listeners.slice().forEach(listener => listener());

Why do we need a new array here?

@raynerpupo
Copy link

raynerpupo commented Apr 10, 2019

@sag1v while that forEach is a blocking operation it doesn't guarantee that a listener() call wouldn't remove a original listener from the listeners array so by copying the list you won't get weird results. 2nd caveat from here: https://redux.js.org/api/store#subscribe.

@raynerpupo
Copy link

@sag1v found the possible reason, please read the 2nd caveat here https://redux.js.org/api/store#subscribe

@sag1v
Copy link

sag1v commented Jun 19, 2020

@raynerpupo For some reason i missed your comments, thanks :)

For other readers, this code snippet can demonstrate what happens if you don't use .slice to create a "snapshot" of the array.
Basically if a listener removes itself from the array, other listeners might not run (due to mutation).

let arr = [func1, func2];
function func1(){
  arr.splice(0, 1) // func1 removes itself from the array
  console.log(1)
}
function func2(){
  console.log(2)
}
arr
//.slice() // if we don't slice here, func2 wont run
.forEach(fn => fn())

// logs: 1

@hacker0limbo
Copy link

2020, starting to learn redux, and got one confusion for this code snippet:

In the createStore function, there should be a third option argument enhancer right? I checked the redux source code and the createStore function might look this:

function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }

  if (typeof enhancer !== 'undefined') {
    if (typeof enhancer !== 'function') {
      throw new Error('Expected the enhancer to be a function.')
    }

    return enhancer(createStore)(reducer, preloadedState)
  }

  // ... dispatch, subscribe, getState, replaceReducer, 
}

In this case we can then pass the applyMiddleware to the createStore function:

const store = createStore(
  rootReducer,
  applyMiddleware(
    thunk,
    logger,
  )

Not sure if my thoughts are reasonable, could someone correct me please?

@taaemoh
Copy link

taaemoh commented Oct 7, 2020

I did the same for my Redux Course to explain how really Redux works and what is the concept. I notice that in a lot of courses about Redux, teachers miss the importance of selectors...

Could u please share some link to your course? is it available somewhere?

@ackvf
Copy link

ackvf commented Jan 23, 2024

Poetry

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