Last active
July 26, 2018 17:28
-
-
Save nicolasparada/14ccc8a0c1ddd5438ab6539e8904d7db to your computer and use it in GitHub Desktop.
Redux Clone (Preact)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const { Component, h, render } = preact | |
const createStore = (reducer, initialState) => { | |
let listeners = [] | |
let currentState = initialState | |
const getState = () => currentState | |
const subscribe = listener => { | |
listeners.push(listener) | |
return () => { | |
listeners = listeners.filter(l => l !== listener) | |
} | |
} | |
const dispatch = action => { | |
const rawDispatch = action => { | |
currentState = reducer(currentState, action) | |
console.log(action, currentState) | |
listeners.forEach(listener => { | |
listener(currentState) | |
}) | |
} | |
if (typeof action === 'function') { | |
return action(dispatch, getState) | |
} else if (typeof action.then === 'function') { | |
return action.then(rawDispatch) | |
} else { | |
return rawDispatch(action) | |
} | |
} | |
dispatch({ type: 'INIT' }) | |
return { getState, subscribe, dispatch } | |
} | |
const combineReducers = reducers => (state = {}, action) => | |
Object.keys(reducers).reduce((nextState, key) => ( | |
nextState[key] = reducers[key](state[key], action), nextState | |
), {}) | |
class Provider extends Component { | |
getChildContext() { | |
return { store: this.props.store } | |
} | |
render(props) { | |
return props | |
} | |
} | |
const shallowEqual = (objA, objB) => { | |
if (objA === objB) { | |
return true | |
} | |
if (typeof objA !== 'object' || objA === null || | |
typeof objB !== 'object' || objB === null) { | |
return false | |
} | |
const keysA = Object.keys(objA) | |
const keysB = Object.keys(objB) | |
if (keysA.length !== keysB.length) { | |
return false | |
} | |
const bHasOwnProperty = Object.prototype.hasOwnProperty.bind(objB) | |
for (let i = 0; i < keysA.length; i++) { | |
if (!bHasOwnProperty(keysA[i]) || objA[keysA[i]] !== objB[keysA[i]]) { | |
return false | |
} | |
} | |
return true | |
} | |
const connect = ( | |
mapStateToProps = () => ({}), | |
mapDispatchToProps = () => ({}) | |
) => Child => class extends Component { | |
constructor(props, context) { | |
super(props, context) | |
this.state = mapStateToProps(context.store.getState(), this.props) | |
this.actions = typeof mapDispatchToProps === 'function' | |
? mapDispatchToProps(context.store.dispatch, this.props) | |
: Object.keys(mapDispatchToProps).reduce((actionCreators, key) => { | |
actionCreators[key] = (...args) => context.store.dispatch(mapDispatchToProps[key](...args)) | |
return actionCreators | |
}, {}) | |
} | |
componentDidMount() { | |
this.unsubscribe = this.context.store.subscribe(newState => { | |
this.setState(mapStateToProps(newState, this.props)) | |
}) | |
} | |
componentDidUnmount() { | |
if (this.unsubscribe) | |
this.unsubscribe() | |
} | |
shouldComponentUpdate(nextProps, nextState) { | |
return !shallowEqual(nextProps, this.props) || !shallowEqual(nextState, this.state) | |
} | |
render(props, state) { | |
return h(Child, Object.assign({}, props, state, this.actions)) | |
} | |
} | |
/** | |
* App code since here. | |
*/ | |
const Display = connect( | |
state => ({ count: state.count }) | |
)(({ count }) => | |
h('span', null, count) | |
) | |
const Controls = connect(undefined, { | |
increment: () => ({ type: 'INCREMENT' }), | |
decrement: () => ({ type: 'DECREMENT' }), | |
})(({ increment, decrement }) => | |
h('div', null, [ | |
h('button', { onClick: increment }, '+'), | |
h('button', { onClick: decrement }, '-'), | |
]) | |
) | |
const AsyncControls = connect(undefined, { | |
incrementAsync: () => new Promise(resolve => { | |
setTimeout(() => { | |
resolve({ type: 'INCREMENT' }) | |
}, 500) | |
}), | |
decrementAsync: () => dispatch => { | |
setTimeout(() => { | |
dispatch({ type: 'DECREMENT' }) | |
}, 500) | |
} | |
})(({ incrementAsync, decrementAsync }) => | |
h('div', null, [ | |
h('button', { onClick: incrementAsync }, '+ (async)'), | |
h('button', { onClick: decrementAsync }, '- (async)') | |
]) | |
) | |
const countReducer = (state = 0, action) => { | |
switch (action.type) { | |
case 'INCREMENT': return state + 1 | |
case 'DECREMENT': return state - 1 | |
default: return state | |
} | |
} | |
const reducer = combineReducers({ | |
count: countReducer | |
}) | |
const store = createStore(reducer) | |
const Root = () => | |
h(Provider, { store }, [ | |
h('div', null, [ | |
h(Display), | |
h(Controls), | |
h(AsyncControls) | |
]) | |
]) | |
render(h(Root), document.body) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment