WHAT I HAVE
LEARNED FROM
REDUX SOURCE CODE
Source Code
Why Redux
- less code
- skillfully
- cool
Redux
State Management
- components communication
- fetch API && store data
- form
- middleware
- redux-form
use case
library
The global state of your application is stored in an object tree within a single store
components communicationfetch API && store data- form
use case
context
Apollo cache
final form ?!
State Management
The global state of your application is stored in an object tree within a single store
Redux
import redux from 'redux';
const initialState = {
counter: 0
}
// Reducer
const rootReducer = ( state = initialState, action) => {
if(action.type === 'INC_COUNTER'){
return {
...state,
counter: state.counter + 1
}
}
if(action.type === 'ADD_COUNTER'){
return {
...state,
counter: state.counter + action.value
}
}
retrun state;
}
// Store
const store = redux.createStore(rootReducer)
console.log(store.getState())
// Subscription
store.subscribe(() => {
console.log('[Subscription]', store.getState())
});
// Dispatching Action
store.dispatch({type: 'INC_COUNTER'});
store.dispatch({type: 'ADD_COUNTER', value: 10});
console.log(store.getState());
Basic
function AppraiseTaskFormProgramPickupTable() {
...
}
const reduxHook = connect(
state => ({
fetchLimit: state.Table.fetchLimit,
pickedPrograms: selectorFromMainTable(state, 'programs') || [],
}),
dispatch => bindActionCreators({
addProgram: programId => arrayPush(FORM_APPRAISE_TASK_PROGRAM_PICKER, 'programs', programId),
removeProgramAtIndex: index => arrayRemove(FORM_APPRAISE_TASK_PROGRAM_PICKER, 'programs', index),
}, dispatch),
);
export default compose(
withRouter,
reduxHook,
)(AppraiseTaskFormProgramPickupTable);
React-redux
- Scalable
- functional
- curry
- recursive
Redux Source Code
import {
createStore,
combineReducers,
compose,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import { reducer as form } from 'redux-form';
import { createBrowserHistory } from 'history';
import {
connectRouter,
routerMiddleware,
} from 'connected-react-router';
import Table from './reducers/Table';
import Card from './reducers/Card';
export const history = createBrowserHistory();
const store = createStore(combineReducers({
Card,
Table,
form,
router: connectRouter(history),
}), {}, compose(
applyMiddleware(
thunk,
routerMiddleware(history),
),
));
export default store;
- Scalable
- functional
- curry
- recursive
Redux Source Code
import {
createStore,
combineReducers,
compose,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import { reducer as form } from 'redux-form';
import { createBrowserHistory } from 'history';
import {
connectRouter,
routerMiddleware,
} from 'connected-react-router';
import Table from './reducers/Table';
import Card from './reducers/Card';
export const history = createBrowserHistory();
const store = createStore(combineReducers({
Card,
Table,
form,
router: connectRouter(history),
}), {}, compose(
applyMiddleware(
thunk,
routerMiddleware(history),
),
));
export default store;
- Scalable
- functional
- curry
- recursive
Redux Source Code
import {
createStore,
combineReducers,
compose,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import { reducer as form } from 'redux-form';
import { createBrowserHistory } from 'history';
import {
connectRouter,
routerMiddleware,
} from 'connected-react-router';
import Table from './reducers/Table';
import Card from './reducers/Card';
export const history = createBrowserHistory();
const store = createStore(combineReducers({
Card,
Table,
form,
router: connectRouter(history),
}), {}, compose(
applyMiddleware(
thunk,
routerMiddleware(history),
),
));
export default store;
// 4.05
src
├── applyMiddleware.js
├── bindActionCreators.js
├── combineReducers.js
├── compose.js
├── createStore.js
├── index.js
└── utils
├── actionTypes.js
├── isPlainObject.js
└── warning.js
struture
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
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)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}
createStore
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
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)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}
createStore
import {
createStore,
combineReducers,
compose,
applyMiddleware,
} from 'redux';
import thunk from 'redux-thunk';
import { reducer as form } from 'redux-form';
import { createBrowserHistory } from 'history';
import {
connectRouter,
routerMiddleware,
} from 'connected-react-router';
import Table from './reducers/Table';
import Card from './reducers/Card';
export const history = createBrowserHistory();
const store = createStore(combineReducers({
Card,
Table,
form,
router: connectRouter(history),
}), {}, compose(
applyMiddleware(
thunk,
routerMiddleware(history),
),
));
export default store;
import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'
export default function createStore(reducer, preloadedState, enhancer) {
if (
(typeof preloadedState === 'function' && typeof enhancer === 'function') ||
(typeof enhancer === 'function' && typeof arguments[3] === 'function')
) {
throw new Error(
'It looks like you are passing several store enhancers to ' +
'createStore(). This is not supported. Instead, compose them ' +
'together to a single function.'
)
}
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)
}
if (typeof reducer !== 'function') {
throw new Error('Expected the reducer to be a function.')
}
let currentReducer = reducer
let currentState = preloadedState
let currentListeners = []
let nextListeners = currentListeners
let isDispatching = false
function ensureCanMutateNextListeners() {
if (nextListeners === currentListeners) {
nextListeners = currentListeners.slice()
}
}
function getState() {
if (isDispatching) {
throw new Error(
'You may not call store.getState() while the reducer is executing. ' +
'The reducer has already received the state as an argument. ' +
'Pass it down from the top reducer instead of reading it from the store.'
)
}
return currentState
}
function subscribe(listener) {
if (typeof listener !== 'function') {
throw new Error('Expected the listener to be a function.')
}
if (isDispatching) {
throw new Error(
'You may not call store.subscribe() while the reducer is executing. ' +
'If you would like to be notified after the store has been updated, subscribe from a ' +
'component and invoke store.getState() in the callback to access the latest state. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
let isSubscribed = true
ensureCanMutateNextListeners()
nextListeners.push(listener)
return function unsubscribe() {
if (!isSubscribed) {
return
}
if (isDispatching) {
throw new Error(
'You may not unsubscribe from a store listener while the reducer is executing. ' +
'See https://redux.js.org/api-reference/store#subscribelistener for more details.'
)
}
isSubscribed = false
ensureCanMutateNextListeners()
const index = nextListeners.indexOf(listener)
nextListeners.splice(index, 1)
currentListeners = null
}
}
function dispatch(action) {
if (!isPlainObject(action)) {
throw new Error(
'Actions must be plain objects. ' +
'Use custom middleware for async actions.'
)
}
if (typeof action.type === 'undefined') {
throw new Error(
'Actions may not have an undefined "type" property. ' +
'Have you misspelled a constant?'
)
}
if (isDispatching) {
throw new Error('Reducers may not dispatch actions.')
}
try {
isDispatching = true
currentState = currentReducer(currentState, action)
} finally {
isDispatching = false
}
const listeners = (currentListeners = nextListeners)
for (let i = 0; i < listeners.length; i++) {
const listener = listeners[i]
listener()
}
return action
}
function replaceReducer(nextReducer) {
if (typeof nextReducer !== 'function') {
throw new Error('Expected the nextReducer to be a function.')
}
currentReducer = nextReducer
dispatch({ type: ActionTypes.REPLACE })
}
dispatch({ type: ActionTypes.INIT })
return {
dispatch,
subscribe,
getState,
replaceReducer,
}
}
createStore
export default function applyMiddleware(...middlewares) {
return createStore => (reducer, ...args) => {
const store = createStore(reducer, ...args)
let dispatch = () => {
throw new Error(
`Dispatching while constructing your middleware is not allowed. ` +
`Other middleware would not be applied to this dispatch.`
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
applyMiddleware
compose
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
Back To The Basic
ES6
curry
recursive
function foo(otherArg, ...args) {
console.log(otherArg); //1
console.log(args); // [2, 3]
}
foo(1,2,3)
ES6 destructing
curry
const add = (x, y) => x + y;
add(9, 8); //17
// create a high-order function
const createAdder = a => b => add(a, b);
const add2 = createAdder(9);
add2(8); // 17
export default function compose(...funcs) {
if (funcs.length === 0) {
return arg => arg
}
if (funcs.length === 1) {
return funcs[0]
}
return funcs.reduce((a, b) => (...args) => a(b(...args)))
}
Redux.compose
export default function compose(...funcs) {
return funcs.reduce(
(accum, curr) => (...args) => accum(curr(...args))
)
}
const foo = arg => arg * 5
const bar = arg => arg + 2
const calculate = compose(foo, bar)(19)
export default function compose(...funcs) {
// funcs = [foo, bar]
return funcs.reduce(
(accum, curr) => (...args) => accum(curr(...args))
)
}
// first
arg => foo(arg)
// second
arg => foo(bar(arg))
const foo = arg => arg * 5
const bar = arg => arg + 2
console.log(compose(foo, bar)(17))
const foo = arg => arg * 5
const bar = arg => arg + 2
console.log(compose(foo, bar)(17)) // 95
recursive
function fibonacci (position) {
if (position <= 2) {
// break
return 1
} else {
return fibonacci(position - 1) + fibonacci(position - 2)
}
}
fibonacci(6) // 5, fibonacci(4) + fibonacci(3)
function createStore(reducer, preloadedState, enhancer) {
...
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
}
...
}
Redux.CreateStore
export default function applyMiddleware(...middlewares) {
return createStore => (reducer, ...args) => {
const store = createStore(reducer, ...args);
let dispatch: Dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
};
const chain = middlewares.map(middleware => middleware(middlewareAPI));
dispatch = compose(...chain)(store.dispatch);
return {
...store,
dispatch
};
};
}
function createStore(reducer, preloadedState, enhancer) {
...
if (typeof enhancer !== 'undefined') {
if (typeof enhancer !== 'function') {
throw new Error('Expected the enhancer to be a function.')
}
return enhancer(createStore)(reducer, preloadedState)
}
}
...
}
export default function applyMiddleware(...middlewares) {
return createStore => (reducer, ...args) => {
const store = createStore(reducer, ...args)
...
return {
...store,
dispatch
}
}
}
// excute
(createStore) => (reducer, ...args) => {...store, dispatch}
applyMiddleware(...middlewares)
enhancer(createStore)(reducer, preloadedState)
(reducer, ...args)
export default function createStore(reducer, preloadedState, enhancer) {
...
/* args' type did not match these condition*/
...
let currentReducer = reducer;
let currentState = preloadedState;
function getState() {
return currentState;
}
function dispatch(action: A) {
currentState = currentReducer(currentState, action);
return action;
}
dispatch({ type: ActionTypes.INIT });
const store = {
dispatch,
subscribe,
getState
};
return store;
}
return store
function createStore(...args) {
const store = {
dispatch,
subscribe,
getState
};
return store;
}
export default function applyMiddleware(...middlewares) {
return createStore => (reducer, ...args) => {
const store = createStore(reducer, ...args)
let dispatch = () => {
throw new Error(
'Dispatching while constructing your middleware is not allowed. ' +
'Other middleware would not be applied to this dispatch.'
)
}
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
Redux.applymiddleware
enhance dispatch
middelware
// redux-thunk
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => (next) => (action) => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const middleware = store => next => action => {
/* Code */
return next(action);
}
export default function applyMiddleware(...middlewares) {
return createStore => (reducer, ...args) => {
...
const middlewareAPI = {
getState: store.getState,
dispatch: (action, ...args) => dispatch(action, ...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
const middleware = store => next => action => {
/* Code */
return next(action);
}
Why do we need "next"
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
...
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
//chain
const [mw1, mw2] = middlewares.map(middleware => middleware(middlewareAPI))
//mw1
function (next) => action => {
...
return next(action)
}
export default function applyMiddleware(...middlewares) {
return createStore => (...args) => {
...
const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
return {
...store,
dispatch
}
}
}
const [mw1, mw2] = middlewares.map(middleware => middleware(middlewareAPI))
// compose(...chain)
next => mw1(mw2(next))
// compose(...chain)(store.dispatch)
mw1(mw2(store.dispatch))
const mw2 = next => action => {
/* Code */
return next(action);
}
// mw2(store.dispatch)
action => store.dispatch(action)
// mw1(mw2(store.dispatch))
action => mw2(action)
One more thing
security
linux-like
have access
to system calls
package
url
package.json
API
same as browser
basic
微 DEMO
Another thing
What I have learned from redux source code
By Jay Chou
What I have learned from redux source code
- 309