-
-
Save gaearon/ffd88b0e4f00b22c3159 to your computer and use it in GitHub Desktop.
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 }; | |
} |
@dlwalsh, result
is created inside the mapValues
function and passed to reduce
as initial value (also called an accumulator). Because it’s created inside mapValues
, it can be mutated and mapValues
would still be a pure function.
@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.
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.
Pure Genius!
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...
Artistic
It's possible to slim it down even further by removing the isDispatching
checks! Then it would be < 90 lines!
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".
I would love to watch a youtube video going over in detail every line of this code.
Awesome. Simple but not simpler 👍
Cool
Haters don't hate you know it's genius... #imnotworthy#
@sabha you probably found it already, but for those who com after, here is connect explained: https://gist.github.com/gaearon/1d19088790e70ac32ea636c025ba424e
Amazingly beautiful code 👍
I'm heavily breathing right now. This is just pure awesomeness!
It's 2018. Is it too late for me to say 'cool'?
Wow!
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?
@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.
@sag1v found the possible reason, please read the 2nd caveat here https://redux.js.org/api/store#subscribe
@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
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?
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?
Poetry
@dbrans The initial
dispatch
function inapplyMiddleware
is used on line 41 within the body of the wrappeddispatch
(i.e.next
) function passed to each middleware.