# mini-redux **Repository Path**: cpranmo/mini-redux ## Basic Information - **Project Name**: mini-redux - **Description**: 手写简易版 redux 实现基本功能方法 - **Primary Language**: JavaScript - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2023-02-05 - **Last Updated**: 2023-04-12 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # redux 源码实现 ## createStore 创建仓库返回一个对象 - dispatch: 分发一个 action 动作 - getState: 得到仓库中当前状态 - subscribe: 注册一个监听器, 监听器是一个无参函数,当分发一个 action 之后会运行注册监听器,该函数会返回一个取消监听函数 - replaceReducer: 替换当前仓库中 reducer 替换时会分发 action 动作 - [$$observable]: 私有方法外部一般情况不能使用 ### 基本使用 > store/action/counterAction.js ```js export const COUNTER_INCR = Symbol("increase"); //! 使用 symbol 能创建每次唯一值 export const COUNTER_DECR = Symbol("decrease"); //! 此文件用于生成 action 函数并且导出 export function counterIncrAction() { return { type: COUNTER_INCR, }; } export function counterDecrAction() { return { type: COUNTER_DECR, }; } ``` > store/reducer/counter.js ```js import { COUNTER_INCR, COUNTER_DECR } from "../action/counterAction"; export function counterReducer(state = 10, action) { switch (action.type) { case COUNTER_INCR: return state + 1; case COUNTER_DECR: return state - 1; default: return state; } } ``` > store/index.js ```js import { createStore } from "redux"; import { counterDecrAction, counterIncrAction } from "./action/counterAction"; import { counterReducer } from "./reducer"; const store = createStore(counterReducer); const unListen = store.subscribe(() => { console.log("监听器1", store.getState()); }); store.dispatch(counterIncrAction()); store.dispatch(counterDecrAction(3)); unListen(); //取消监听 ``` ### 源码实现 > redux/utils/symbol-observable.js ```js // 产出唯一值 export const $$observable = (() => (typeof Symbol === "function" && Symbol.observable) || "@@observable")(); // 函数自执行结果返回出去 ``` > redux/createStore.js ```js import { $$observable } from "./utils/symbol-observable"; import { isPlainObject } from "./utils/isPlainObject"; import { ActionTypes } from "./utils/actionTypes"; /** * 创建仓库数据 * @param {*} reducer * @param {*} defaultState 默认状态 * @param {*} enhancer 中间件 */ export function createStore(reducer, defaultState, enhancer) { // 判断传入数据可用性并确定参数位置 if ( (typeof defaultState === "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 defaultState === "function" && typeof enhancer === "undefined") { enhancer = defaultState; defaultState = undefined; } if (typeof enhancer !== "undefined") { if (typeof enhancer !== "function") { // 中间件有值但是不是函数直接抛出错误 throw new Error("Expected the enhancer to be a function."); } // enhancer 就是 applyMiddleware 中间件函数得到返回函数 return enhancer(createStore)(reducer, defaultState); // 中间件使用用于增强函数 } // reducer 不是函数直接抛出错误 if (typeof reducer !== "function") { throw new Error("Expected the reducer to be a function."); } let currentReducer = reducer; // 保存 reducer let currentState = defaultState; // 保存默认状态 const listeners = []; /** * 派发更改数据 * @param {*} action 描述对象 * @returns */ function dispatch(action) { // action 描述对象必须是平面对象 if (!isPlainObject(action)) { throw new Error( `Actions must be plain objects. Use custom middleware for async actions.` ); } // 验证描述对象必须有 type 属性 if (typeof action.type === "undefined") { throw new Error( `Actions may not have an undefined "type" property. Have you misspelled a constant?` ); } try { currentState = currentReducer(currentState, action); // 更新状态 } catch (e) { throw new Error(e.toString()); } // 运行所有订阅者 for (const listener of listeners) { listener(); } return action; } /** * 添加一个监听器 * @param {*} listener 监听函数 * @returns 取消监听函数 */ function subscribe(listener) { // 判断传入监听器是否为函数 if (typeof listener !== "function") { throw new Error("Expected the listener to be a function."); } let isSubscribed = false; // 加标识多次取消运行 listeners.push(listener); // 存储订阅者 // 返回取消监听函数 return () => { if (isSubscribed) return; const index = listeners.indexOf(listener); listeners.splice(index, 1); isSubscribed = true; }; } /** * 获取当前仓库状态数据 * @returns 仓库数据 */ function getState() { return currentState; } /** * 替换当前仓库 reducer * @param {*} nextReducer */ function replaceReducer(nextReducer) { // 判断 reducer 是否函数 if (typeof nextReducer !== "function") { throw new Error("Expected the nextReducer to be a function."); } currentReducer = nextReducer; // 替换当前 reducer dispatch({ type: ActionTypes.REPLACE }); // 派发一次 } function observable() { const outerSubscribe = subscribe; // 监听函数 return { subscribe(observer) { if (typeof observer !== "object" || observer === null) { throw new TypeError( "Expected the observer to be an object." ); } function observeState() { if (observer.next) { observer.next(getState()); } } observeState(); const unsubscribe = outerSubscribe(observeState); return { unsubscribe }; }, [$$observable]() { return this; }, }; } //! 初始化需派发 dispatch({ type: ActionTypes.INIT }); return { dispatch, subscribe, getState, replaceReducer, [$$observable]: observable, //? 此方法为私有外部一般操作获取不到 }; } ``` ## bindActionCreators 内部自动 dispatch 其他的保持一致 ### 基本使用 > store/index.js ```js import { createStore, bindActionCreators } from "redux"; const COUNTER_INCR = Symbol("increase"); const COUNTER_DECR = Symbol("decrease"); function counterIncrAction() { return { type: COUNTER_INCR, }; } function counterDecrAction() { return { type: COUNTER_DECR, }; } function counterReducer(state = 10, action) { switch (action.type) { case COUNTER_INCR: return state + 1; case COUNTER_DECR: return state - 1; default: return state; } } // 创建一个仓库 const store = createStore(counterReducer); // 添加监听器 store.subscribe(() => { console.log("仓库状态==>", store.getState()); }); // 使用方法一:参数一传递为函数 const counterIncr = bindActionCreators(counterIncrAction, store.dispatch); counterIncr(); // 使用方法二:参数一传递为对象 const counterActions = { counterIncrAction, counterDecrAction }; const counter = bindActionCreators(counterActions, store.dispatch); counter.counterDecrAction(); // 触发对应操作 ``` ### 源码实现 > redux/bindActionCreators.js ```js /** * 增强函数分发功能 * @param {*} actionCreator 单个 action * @param {*} dispatch 派发函数 * @returns */ export function bindActionCreator(actionCreator, dispatch) { return (...args) => { return dispatch(actionCreator.apply(this, args)); }; } /** * 增强派发函数功能 * @param {*} actionCreators 对象或者函数 * @param {*} dispatch 派发函数 */ export function bindActionCreators(actionCreators, dispatch) { // 第一个参数为函数时 if (typeof actionCreators === "function") { return bindActionCreator(actionCreators, dispatch); } // 不是对象或者函数时直接抛错处理 if (typeof actionCreators !== "object" || actionCreators === null) { throw new TypeError( "bindActionCreators expected an object or a function" ); } // 返回出去的结果 const boundActionCreators = {}; // 遍历对象获取每个 action 描述 for (const key in actionCreators) { const actionCreator = actionCreators[key]; // 取出对象属性值 // 验证属性值必须为函数, 非函数不做处理 if (typeof actionCreator === "function") { boundActionCreators[key] = bindActionCreator( actionCreator, dispatch ); } } return boundActionCreators; } ``` ## combineReducers 组装 reducers 返回一个大 reducer,数据使用一个对象表示,对象的属性名和传递的参数对象保持一致 ### 基本使用 > store/action/userAction.js ```js export const ADDUSER = Symbol("add-user"); export const DELETEUSER = Symbol("delete-user"); export const UPDATEUSER = Symbol("update-user"); export const createAddUserAtion = (user) => ({ type: ADDUSER, payload: user, }); export const createDeleteUserAction = (id) => ({ type: DELETEUSER, payload: id, }); export const createUpdateUserAtion = (id, newUser) => ({ type: UPDATEUSER, payload: { ...newUser, id, }, }); ``` > store/reducer/user.js ```js import { ADDUSER, DELETEUSER, UPDATEUSER } from "../action/userAction"; function uuid() { return Math.random().toString(36).slice(-6); } const initialState = [ { uid: uuid(), name: "用户1", age: 18 }, { uid: uuid(), name: "用户2", age: 19 }, ]; export const userReducer = (state = initialState, { type, payload }) => { //! 注意使用 createStore 创建合并后的 reducer 时发现有两次额外 type 打印时使用 combineReducers 合并 reducer 时会校验传入每个 reducer 方法是否符合标准 switch (type) { case ADDUSER: return [...state, payload]; case DELETEUSER: return state.filter((item) => item.uid !== payload); case UPDATEUSER: return state.map((item) => item.uid === payload.uid ? { ...item, ...payload } : item ); default: return state; } }; ``` > store/reducer/index.js ```js import { combineReducers } from "redux"; // 用于合并 reducer import { counterReducer } from "./counter"; import { userReducer } from "./user"; // 合并 reducer 创建一个大仓库 export const reducer = combineReducers({ counter: counterReducer, user: userReducer, }); ``` > store/index.js ```js import { createStore } from "redux"; import { counterDecrAction, counterIncrAction } from "./action/counterAction"; import { reducer } from "./reducer"; const store = createStore(reducer); console.log(store.getState()); ``` ### 源码实现 > redux/utils/actionTypes.js ```js /** * 得到 len 长度随机字符串 * @param {*} len * @returns */ export function getRandomString(len) { return Math.random().toString(36).substring(len).split("").join("."); } /** * 得到 action 中 type 默认值 * @returns */ export const ActionTypes = { INIT: `@@redux/INIT${getRandomString(7)}`, REPLACE: `@@redux/REPLACE${getRandomString(7)}`, PROBE_UNKNOWN_ACTION: () => `@@redux/PROBE_UNKNOWN_ACTION${getRandomString(7)}`, }; ``` > redux/utils/isPlainObject.js ```js /** * 判断描述对象是否平面对象 * @returns */ export function isPlainObject(obj) { if (typeof obj !== "object" || obj === null) return false; let proto = obj; while (Object.getPrototypeOf(proto) !== null) { proto = Object.getPrototypeOf(proto); } return Object.getPrototypeOf(obj) === proto; // 平面对象需要 obj.__proto__ === Object.prototype } ``` > redux/combineReducers.js ```js import { ActionTypes } from "./utils/actionTypes"; import { isPlainObject } from "./utils/isPlainObject"; /** * 尝试验证 reducers 写的是否合理 * @param {*} reducers */ function assertReducerShape(reducers) { // 获取枚举自身属性 Object.keys(reducers).forEach((key) => { const reducer = reducers[key]; let state = reducer(undefined, { type: ActionTypes.INIT }); if (state === undefined) { throw new TypeError("reducers must not return undefined"); // reducer 返回不能是 undefined } state = typeof reducer(undefined, { type: ActionTypes.PROBE_UNKNOWN_ACTION, }); if (state === undefined) { throw new TypeError("reducers must not return undefined"); } }); } /** * 将多个 reducer 合并为一个 * @param {*} reducers 需合并 reducer 状态对象 * @returns */ export function combineReducers(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"); // 不是平面对象也抛错 } const reducerKeys = Object.keys(reducers); // 得到对象所有属性名 // 遍历传入对象属性名过滤掉属性值不是 reducer 函数 // const finalReducers = {}; // for (let i = 0; i < reducerKeys.length; i++) { // const key = reducerKeys[i]; // if (typeof reducers[key] === 'undefined') { // console.warn(`No reducer provided for key "${key}"`) // } // if (typeof reducers[key] === 'function') { // finalReducers[key] = reducers[key]; // } // } // 等价于上面写法 const finalReducers = reducerKeys.reduce((a, b) => { if (typeof reducers[b] === "undefined") { console.warn(`No reducer provided for key "${b}"`); } if (typeof reducers[b] === "function") { a[b] = reducers[b]; } return a; }, {}); // 收集验证错误消息 let shapeAssertionError; try { // 进行两次验证 reducer 返回结果是否 undefined 也就是判断写的 reducer 是否符合标准不返回 undefined 则标准 assertReducerShape(finalReducers); } catch (e) { shapeAssertionError = e; // 收集错误信息 } return (state = {}, action) => { // 验证 reducer 失败直接抛错 if (shapeAssertionError) { throw shapeAssertionError; } let hasChanged = false; const newState = {}; for (const key in finalReducers) { const reducer = finalReducers[key]; // 获取到对应 reducer 方法 const previousStateForKey = state[key]; // reducer 对应的状态数据 const newStateForKey = reducer(previousStateForKey, action); // reducer 处理后状态数据返回值(reducer对应状态) // 再次判断是否更新仓库值是否 undefined if (typeof newStateForKey === "undefined") { throw new TypeError("reducers must not return undefined"); } newState[key] = newStateForKey; hasChanged = hasChanged || newStateForKey !== previousStateForKey; // 判断前后状态是否相同 } hasChanged = hasChanged || Object.keys(finalReducers).length !== Object.keys(state).length; return hasChanged ? newState : state; }; } ``` ## applyMiddleware middleware: 本质是一个调用后可以得到 dispatch 创建函数 compose:函数组合,将一个数组中的函数进行组合形成一个新的函数,该函数调用时,实际上是反向调用之前组合的函数 (经典题) ### 基本使用 > store/index.js ```js import { createStore, applyMiddleware } from "redux"; import { counterDecrAction, counterIncrAction } from "./action/counterAction"; import { reducer } from "./reducer"; // 中间件 const logger1 = (store) => (next) => (action) => { //! store 非完整仓库对象只包含 getState, dipatch 两个属性 //! next 实际就是 dispatch 派发函数 //! action 实际就是派发的动作类型 // console.log(store, next, action); console.log("旧数据1==>", store.getState()); next(action); console.log("新数据1==>", store.getState()); }; // 创建方式1 (推荐使用方便) const store = createStore(reducer, applyMiddleware(logger1, logger2)); // 创建方式2 (applyMiddleware方法实现是根据下面函数形式实现,上面只是一种变换最终还是会根据下面形式完成) // const store = applyMiddleware(logger1, logger2)(createStore)(reducer); store.dispatch(counterIncrAction()); store.dispatch(counterDecrAction()); ``` ### 源码实现 > redux/applyMiddleware.js ```js /** * 中间件实现 * ! 核心思想就是保留之前 dispatch,覆盖之前方法并在里面调用之前保存下来方法(也就是组合一个大的 dipacth) 类似切片 * @param {...any} middlewares 每个中间件函数 * @returns */ export function applyMiddleware(...middlewares) { return (createStore) => (...args) => { // args 为 createStore 方法中的形参也就包含 reducer 和 defaultState const store = createStore(...args); // 创建仓库 // 重写 dispatch 函数 let dispatch = () => { throw new Error( "Dispatching while constructing your middleware is not allowed. " + "Other middleware would not be applied to this dispatch." ); }; // 取原 store 中部分方法 const middlewareAPI = { getState: store.getState, dispatch: (...args) => dispatch(...args), }; // 遍历中间件将切取的 store 中部分方法传入作为参数 const chain = middlewares.map((middleware) => middleware(middlewareAPI) ); //? 此方法中难点理解(通过 compose 函数组合成大的 dispatch ) dispatch = compose(...chain)(store.dispatch); return { ...store, dispatch, // 覆盖 store 中 dispatch 方法 }; }; } ``` > redux/compose.js (经典手写题) ```js /** * 将多个函数组合为复合函数 (经典函数通用) * @param {...any} funcs */ export function compose(...funcs) { if (funcs.length === 0) { return (args) => args; // 如果没有要组合的函数,则返回的函数原封不动的返回参数 (args 就是 store.dispatch) } if (funcs.length === 1) { return funcs[0]; // 组合函数只有一个 } // return function (...args) { // let lastReturn = null; // 函数返回值得到下一个中间件参数 action // for (let i = funcs.length - 1; i >= 0; i--) { // const func = funcs[i]; // func 就是 next => acton => {} 函数 // if (i === funcs.length - 1) { // 数组最后一项 // lastReturn = func(...args); // lastReturn 实际就是 action => {} 函数形参为外部传入 // } else { // lastReturn = func(lastReturn); // } // } // return lastReturn // 最终 action // } return funcs.reduce( (a, b) => (...args) => a(b(...args)) ); //! 等价于上面方法 } ``` ## 详细代码 手写实现代码放在 src/redux 目录结构下