手書きReduxソース


  • 入口関数index.js
  • export { default as createStore } from "./createStore"
    export { default as bindActionCreators } from "./bindActionCreators"
    export { default as combineReducers } from "./combineReducers"
    export { default as applyMiddleware } from "./applyMiddleware"
    export { default as compose } from "./compose"
    
  • utils/アクションTypes.js
  • /**
     *               
     * @param {*} length 
     */
    function getRandomString(length) {
      return Math.random().toString(36).substr(2, length).split("").join(".")
    }
    
    export default {
      INIT() {
        return `@@redux/INIT${getRandomString(6)}`
      },
      UNKNOWN() {
        return `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(6)}`
      }
    }
    
  • utils/isPlainObject.js
  • /**
     *            plain-object
     * @param {*} obj 
     */
    export default function isPlainObject(obj) {
      if (typeof obj !== "object") {
        return false;
      }
      return Object.getPrototypeOf(obj) === Object.prototype;
    }
    
  • createStore.js
  • import ActionTypes from "./utils/ActionTypes"
    import isPlainObject from "./utils/isPlainObject"
    
    /**
     *   createStore   
     * @param {function} reducer reducer
     * @param {any} defaultState       
     */
    export default function createStore(reducer, defaultState, enhanced) {
        //enhanced  applymiddleware     
        if (typeof defaultState === "function") {
            //                 
            enhanced = defaultState;
            defaultState = undefined;
        }
        if (typeof enhanced === "function") {
            //  applyMiddleWare     
            return enhanced(createStore)(reducer, defaultState);
        }
    
        let currentReducer = reducer, //     reducer
            currentState = defaultState; //        
    
        const listeners = [];  //        (   )
    
        function dispatch(action) {
            //  action
            if (!isPlainObject(action)) {
                throw new TypeError("action must be a plain object");
            }
            //  action type      
            if (action.type === undefined) {
                throw new TypeError("action must has a property of type");
            }
            currentState = currentReducer(currentState, action)
            //        (   )
            for (const listener of listeners) {
                listener();
            }
        }
    
        function getState() {
            return currentState;
        }
    
        /**
         *        (   )
         */
        function subscribe(listener) {
            listeners.push(listener); //          
            let isRemove = false;//        
            return function () {
                if (isRemove) {
                    return;
                }
                // listener      
                const index = listeners.indexOf(listener);
                listeners.splice(index, 1);
                isRemove = true;
            }
        }
    
        //     ,         action
        dispatch({
            type: ActionTypes.INIT()
        })
    
        return {
            dispatch,
            getState,
            subscribe
        }
    }
    
  • cobineReduces.js
  • import isPlainObject from "./utils/isPlainObject"
    import ActionTypes from "./utils/ActionTypes"
    
    function validateReducers(reducers) {
      if (typeof reducers !== "object") {
        throw new TypeError("reducers must be an object");
      }
      if (!isPlainObject(reducers)) {
        throw new TypeError("reducers must be a plain object");
      }
      //  reducer        undefined
      for (const key in reducers) {
        if (reducers.hasOwnProperty(key)) {
          const reducer = reducers[key];//  reducer
          //       type 
          let state = reducer(undefined, {
            type: ActionTypes.INIT()
          })
          if (state === undefined) {
            throw new TypeError("reducers must not return undefined");
          }
          state = reducer(undefined, {
            type: ActionTypes.UNKNOWN()
          })
          if (state === undefined) {
            throw new TypeError("reducers must not return undefined");
          }
        }
      }
    }
    
    export default function (reducers) {
      //1.   
      validateReducers(reducers);
      /**
       *       reducer  
       */
      return function (state = {}, action) {
        const newState = {}; //        
        for (const key in reducers) {
          if (reducers.hasOwnProperty(key)) {
            const reducer = reducers[key];
            newState[key] = reducer(state[key], action);
          }
        }
        return newState; //    
      }
    }
    
  • ビッドアクションクリエーター.js
  • export default function (actionCreators, dispatch) {
      if (typeof actionCreators === "function") {
        return getAutoDispatchActionCreator(actionCreators, dispatch);
      }
      else if (typeof actionCreators === "object") {
        const result = {}; //    
        for (const key in actionCreators) {
          if (actionCreators.hasOwnProperty(key)) {
            const actionCreator = actionCreators[key]; //        
            if (typeof actionCreator === "function") {
              result[key] = getAutoDispatchActionCreator(actionCreator, dispatch);
            }
          }
        }
        return result;
      }
      else {
        throw new TypeError("actionCreators must be an object or function which means action creator")
      }
    }
    
    /**
     *          action    
     */
    function getAutoDispatchActionCreator(actionCreator, dispatch) {
      return function (...args) {
        const action = actionCreator(...args)
        dispatch(action);
      }
    }
    
  • compses.js
  • export default function compose(...funcs) {
      if (funcs.length === 0) {
        return args => args; //          ,               
      }
      else if (funcs.length === 1) {
        //          
        return funcs[0];
      }
    
      return funcs.reduce((a, b) => (...args) => a(b(...args)))
    
      // return function (...args) {
      //     let lastReturn = null; //           ,            
      //     for (let i = funcs.length - 1; i >= 0; i--) {
      //         const func = funcs[i];
      //         if (i === funcs.length - 1) {//      
      //             lastReturn = func(...args)
      //         }
      //         else {
      //             lastReturn = func(lastReturn)
      //         }
      //     }
      //     return lastReturn;
      // }
    }
    
  • appyMiddleware.js
  • import compose from "./compose"
    /**
     *      
     * @param  {...any} middlewares       
     */
    export default function (...middlewares) {
      return function (createStore) { //         
        //           
        return function (reducer, defaultState) {
          //    
          const store = createStore(reducer, defaultState);
          let dispatch = () => { throw new Error("       dispatch") };
          const simpleStore = {
            getState: store.getState,
            dispatch: store.dispatch
          }
          // dispatch  
          //       ,    dispatch       
          const dispatchProducers = middlewares.map(mid => mid(simpleStore));
          dispatch = compose(...dispatchProducers)(store.dispatch);
          return {
            ...store,
            dispatch
          }
        }
      }
    }