动手写一个redux,react-redux以及对中间件的扩展(异步thunk,打印logger)

手撸的乞丐版如下,仅实现了最基础的功能,订阅,派遣,获取三个功能,不过已经可以简单使用了,做个计数器是没啥问题,而且可以更简单粗暴的看到redux的核心功能

注意:该篇篇幅较长,建议收藏后慢慢看,也可以直接去github上拉下来自己试一试~《github传送门》

--------------------------------------------------------------正文开始-------------------------------------------------------------
简易版redux,真正的redux源码在文章最后面,源码较长,将近300行,刨去注释和兼容开发版不到100行,处理的场景比较多。我自己写的这个redux仅实现了它的核心功能,没有去管太多的兼容问题,但是代码少一点,大家对它的接受程度也会高一点,希望大家能够看懂。

const REDUX_INIT_TYPE = "@@REDUX_INIT_TYPE"
export function createStore(reducer, preloadedState, enhancer) {
	/**
	 * 为什么要交换一下?
	 * 看名字,可以大概知道第二个参数preloadedState代表了预设的State,如果不传就是空的,
	 * 也就是state的默认值,这个在初始化的时候有用到,也就是dispatch中对应的第一次reducer
	 *
	 * 一般的reducer会有default选项,会返回默认的state,而且在定义reducer的时候,会给state传一个默认的值,也就导致了,如果preloadedState不传,就会默认读取用户自己设置的init_state
	 *
	 * 当传了preloadedState,但是没有传enhancer,且preloadedState是一个方法,那么说明用户可能传错了,那么,需要将第二个参数移到第三个参数上,并将preloadedState置为undefined
	 * 否则初始化state的时候,是无法正常初始化的(参数有默认值,但是接收到的不是undefined的时候,就不会自动设置为默认值!
	 */
	if (typeof preloadedState === "function" && typeof enhancer === "undefined") {
		enhancer = preloadedState;
		preloadedState = undefined;
	}

	// 如果强化器是一个方法,那么直接返回强化器强化的createStore对reducer的执行操作,也就是包装一下
	if (typeof enhancer === "function") {
		return enhancer(createStore)(reducer);
	}

	let currentState = preloadedState;
	const currentListenerList = [];

	function getState() {
		return currentState;
	}

	/**
	 * 派发操作,让reducer执行该操作对state进行更新
	 * 之后 依次调用所有的监听器
	 */
	function dispatch(action) {
        currentState = reducer(currentState, action);
		currentListenerList.forEach((listener) => listener());
		return action;
	}

	// 订阅监听器
	function subscribe(listener) {
		currentListenerList.push(listener);
	}

	// 初始化currentState,因为该type在reducer中找不到对应的操作,所以走的默认值
	dispatch({
		type: REDUX_INIT_TYPE,
	});

	return {
		getState,
		dispatch,
		subscribe,
	};
}

写到这里,redux已经可以用了,不过只能用同步的,对于setTimeout那种是不支持的。

// 申请中间件
export function applyMiddleware(...middleWares) {
	return (createStore) => (...args) => {
		// 根据传进来的参数,创建一个新的store
		let store = createStore(...args);

		// 先把原来的dispatch保存下来,后面需要引用他
		let oldDispatch = store.dispatch;

		/**
         * 把中间件都执行一下,然后返回一个新的数组,
         * 中间件执行后都会返回一个函数,接受dispatch作为参数
         * 如 thunk的源码
         * function createThunkMiddleware(extraArgument) {
         *      return ({ dispatch, getState }) => next => action => {
         *          if (typeof action === 'function') {
         *              return action(dispatch, getState, extraArgument);
         *          }
         *          return next(action);
         *      };
         * }
         * export default createThunkMiddleware();
         * 实际上返回的就是
         * ({ dispatch, getState }) => next => action => {
         *      if (typeof action === 'function') {
         *          return action(dispatch, getState);
         *      }
         *      return next(action);
         * };
         * 这里面接受一个对象参数,{dispatch, getState}
         * 返回一个函数,next为参数,这个next实际上就是一个dispatch,同样接受一个action作为参数,返回一个结果
         * next => action => {
         *      if (typeof action === 'function') {
         *          return action(dispatch, getState);
         *      }
         *      return next(action);
         * }
         * 与之前写的dispatch做个对比
         * function dispatch(action) {
         *      currentState = reducer(currentState, action);
         *      return action;
         * }
         * 基本造型是一样的,只不过这里面是对dispatch进行了一次强化,
         * 如果dispatch接受的action是一个函数,那么就先执行一下这个函数,再返回结果,否则,就直接返回执行后的结果
         * 这也就是最开始为什么dispatch要返回一个action的原因,用于将这个action再传给下一个中间件,也就是这一次执行的所有的中间件都是用的同一个action
         * 
         * 每个中间件都是接受一个dispatch做参数,执行完之后,都是返回一个action(执行操作),当做下一个中间件的dispatch的action
         * 
         * 所以先创建一个通用数据
         * 这里的dispatch是最原始的dispatch
         */
        const midData = {
            getState: store.getState,
            dispatch: (...args) => oldDispatch(...args)
        }
        let newMiddleWares = middleWares.map(mw => mw(midData));

        // 然后利用middleWares对store的dispath进行强化
        let newDispatch = compose(...newMiddleWares)(store.dispatch);

        return {
            ...store,
            dispatch: newDispatch
        }
	};
}

写完applyMiddleWare,就可以支持第三方中间件了,比如异步操作(setTimeout,ajax等),打印state数据等……

/**
 * 顺序执行中间件,由[a,b] => a(b(...args));
 * 这个方法简直是太酷了,不看源码我估计是不会想到用这个方法来做函数递归调用了。
 * 还是对数组的方法不太熟悉啊!
 */
export function compose(...funcs) {
	return funcs.reduce((prevFn, currentFn) => (...args) => currentFn(prevFn(...args)));
}

自己手写的logger中间件,真正的redux-logger很炫酷,它对console.log进行了一些美化,具体的美化可以跳转 redux-logger的core.js
我之前也写过一个console.log的简易用法指北,有兴趣的可以去看看 《传送门》

export const fake_logger = ({dispatch, getState}) => nextDispatch => action => {
	console.log(action.type + "执行了!!!");
	console.log(getState())
	return nextDispatch(action);
}
export const fake_thunk = ({dispatch, getState}) => nextDispatch => action => {
	if(typeof action === "function") {
		return action(dispatch, getState)
	}
	return nextDispatch(action);
}

写到这里,redux部分已经完成了,在我自己写的例子中可以“完美”运行,开心一下,但是在react中使用起来很麻烦,每个组件都需要在state变化后手动去调用this.forceUpdate()对组件进行强制刷新,很不方便,所以才有了react-redux,react-redux就很好的支持了redux在react中的使用。下面开始搞react-redux。

先看看怎么使用的

// 父组件
const App = () => {
  return (
    <Provider store={store}>
      <RootRouter />
    </Provider>
  );
}
// 子组件
const Wrapper = ({ counter }) => {
	return (
		<p>{counter.xxx}</p>
	)
}
export default connect(({ counter }) => ({
    counter
}), (dispath) => ({
    add() {
        dispath({
        	type: "add"
		})
    }
}))(Wrapper);

当我看到Provider的时候,我第一时间就想到了context,不熟悉的同学可以去看一下我之前写的关于context的文章《传送门》,然后我就按照自己的猜测去写了一版,代码如下

import React, { createContext, Component } from "react";

const { Provider, Consumer } = createContext(null);

export class SProvider extends Component {
	constructor(props) {
		super(props);
	}

	componentDidMount() {
		this.props.store.subscribe(() => {
			this.forceUpdate();
		});
	}

	render() {
		/**
		 * 刚开始我是直接把this.props.store传给value的,但是会导致组件不刷新,我认为应该是react在做浅比较的时候,认为方法都是一样的,因为state是通过getState方法获取的
		 * 所以我把state和dispatch提出来,这样对比的时候,state就会变化了(事实证明也成功了。。)
		 * 我还没看react-redux的源码,,如果有错误,欢迎大家指出来,留言给我说,求轻喷T_T
		 */
		return <Provider value={{
            state: this.props.store.getState(),
            dispatch: this.props.store.dispatch
        }}>{this.props.children}</Provider>;
	}
}

/**
 * connect是一个高阶函数,他对接收到的组件经行了一次二次封装,并将用户传入的参数都用redux执行一次,再以props的形式传给组件
 * @params {function} getStateFn 接受state作为参数,返回一个用户自己定义的对象
 * @params {function | object} dispatchs 有可能是一个对象,内部是key: action
 * function接受dispatch作为参数,返回一个用户自己定义的对象
 * 都会返回key: () => dispatch(action);
 */
export function connect(getStateFn, dispatchs) {
	return (PropsComponent) => (props) => (
		<Consumer>
			{(store) => {
                let newDispatchs = {};

                // 如果传进来的是一个对象,那么需要对这个对象的每个元素用dispatch包装一下
                if(typeof dispatchs === "object") {
                    Object.keys(dispatchs).forEach((item) => {
                        newDispatchs[item] = () => store.dispatch(dispatchs[item])
                    })
                } else if(typeof dispatchs === "function") {
                    newDispatchs = dispatchs(store.dispatch);
                }

				return <PropsComponent {...getStateFn(store.state)} {...newDispatchs} {...props} />;
			}}
		</Consumer>
	);
}

反正是可以运行了,待我后面再看看react-redux的源码,如果有出入,再来更改,我相信自己的判断!干吧得!冲鸭!

大家能看到这句话是真的不容易,可否帮我点个赞呀~十分感谢!!!

真正的源码开始,有对源码的注释的翻译,有兴趣的可以看一下。或者去github直接下载他的源码包《传送门》

import $$observable from 'symbol-observable'
import ActionTypes from './utils/actionTypes'
import isPlainObject from './utils/isPlainObject'


/**
* Creates a Redux store that holds the state tree.
* 创建一个状态管理的容器,来存储这个状态树
* The only way to change the data in the store is to call `dispatch()` on it.
* 只有一种方法去更新这个容器里的状态,就是调用dispatch去通知他
*
* There should only be a single store in your app. To specify how different
* parts of the state tree respond to actions, you may combine several reducers
* into a single reducer function by using `combineReducers`.
*
* @param {Function} reducer A function that returns the next state tree, given
* the current state tree and the action to handle.
* 给定当前状态树和要处理的操作,返回下一个状态树的函数。
*
* @param {any} [preloadedState] The initial state. You may optionally specify it
* to hydrate the state from the server in universal apps, or to restore a
* previously serialized user session.
* If you use `combineReducers` to produce the root reducer function, this must be
* an object with the same shape as `combineReducers` keys.
*
* 初始状态。您可以选择在universal apps中指定它来从服务器获取状态,或者恢复以前序列化的用户会话。如果您使用‘combineReducers’来生成根还原函数,那么这个对象的形状必须与‘combineReducers’键相同。
*
* @param {Function} [enhancer] The store enhancer. You may optionally specify it
* to enhance the store with third-party capabilities such as middleware,
* time travel, persistence, etc. The only store enhancer that ships with Redux
* is `applyMiddleware()`.
* enhancer是增强器的意思
* 存储增强器。您可以有选择地指定它,以使用第三方功能(如中间件、时间旅行、持久性等)增强存储。Redux附带的惟一存储增强器是“applyMiddleware()”。
*
* @returns {Store} A Redux store that lets you read the state, dispatch actions
* and subscribe to changes.
*/
export default function createStore(reducer, preloadedState, enhancer) {
 if (
   (typeof preloadedState === 'function' && typeof enhancer === 'function') ||
   (typeof enhancer === 'function' && typeof arguments[3] === 'function')
 ) {
   // 看起来您正在传递几个存储增强器(store enhancers)给createStore()
   // 这是不被支持的
   // 把他们组合在一起,成为有一个简单的函数
   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.'
   )
 }

 // 当第二个参数preloadedState是一个方法,且没有第三个参数,就把第二个参数和第三个参数交换值
 /**
  * 为什么要交换一下?
  * 看名字,可以大概知道第二个参数preloadedState代表了预设的State,如果不传就是空的,
  * 也就是state的默认值,这个在初始化的时候有用到,也就是dispatch中对应的第一次reducer
  *
  * 一般的reducer会有default选项,会返回默认的state,而且在定义reducer的时候,会给state传一个默认的值,也就导致了,如果preloadedState不传,就会默认读取用户自己设置的init_state
  *
  * 当传了preloadedState,但是没有传enhancer,且preloadedState是一个方法,那么说明用户可能传错了,那么,需要将第二个参数移到第三个参数上,并将preloadedState置为undefined
  * 否则初始化state的时候,是无法正常初始化的(参数有默认值,但是接收到的不是undefined的时候,就不会自动设置为默认值!
  */
 if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
   enhancer = preloadedState
   preloadedState = undefined
 }

 /**
  * 当enhancer不是空的,结合上面的代码就可以得出,如果有后面的两个参数之一,就会执行当前语句
  * 所以也就是说,如果第二个参数为方法,则默认就会变成第三个参数
  * enhancer应该是一个高阶组件,利用中间件对createStore进行一次封装
 **/
 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') {
   // 预料的reducer应该是一个函数
   throw new Error('Expected the reducer to be a function.')
 }

 /**
  * 如果preloadedState不是一个函数,而是一个值或者对象什么的,才会走到这里
 **/
 let currentReducer = reducer
 let currentState = preloadedState
 let currentListeners = []
 let nextListeners = currentListeners
 let isDispatching = false


 /**
  * This makes a shallow copy of currentListeners so we can use
  * nextListeners as a temporary list while dispatching.
  * 这将创建一个currentListener的浅拷贝,因此我们可以在分派时将nextListener用作临时列表
  * This prevents any bugs around consumers calling
  * subscribe/unsubscribe in the middle of a dispatch.
  */
 function ensureCanMutateNextListeners() {
   if (nextListeners === currentListeners) {
     nextListeners = currentListeners.slice()
   }
 }


 /**
  * Reads the state tree managed by the store.
  *
  * @returns {any} The current state tree of your application.
  */
 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
 }


 /**
  * Adds a change listener. It will be called any time an action is dispatched,
  * and some part of the state tree may potentially have changed. You may then
  * call `getState()` to read the current state tree inside the callback.
  * 添加更改侦听器。
  * 在分派操作的任何时候都会调用它,并且状态树的某些部分可能已经更改。
  * 然后可以调用' getState() '来读取回调函数中的当前状态树。
  * 
  * You may call `dispatch()` from a change listener, with the following
  * caveats:
  *
  * 1. The subscriptions are snapshotted just before every `dispatch()` call.
  * If you subscribe or unsubscribe while the listeners are being invoked, this
  * will not have any effect on the `dispatch()` that is currently in progress.
  * However, the next `dispatch()` call, whether nested or not, will use a more
  * recent snapshot of the subscription list.
  *
  * 2. The listener should not expect to see all state changes, as the state
  * might have been updated multiple times during a nested `dispatch()` before
  * the listener is called. It is, however, guaranteed that all subscribers
  * registered before the `dispatch()` started will be called with the latest
  * state by the time it exits.
  *
  * @param {Function} listener A callback to be invoked on every dispatch.
  * @returns {Function} A function to remove this change listener.
  */
 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
   }
 }


 /**
  * Dispatches an action. It is the only way to trigger a state change.
  * 分派一个动作。这是触发状态更改的唯一方法。
  *
  * The `reducer` function, used to create the store, will be called with the
  * current state tree and the given `action`. Its return value will
  * be considered the **next** state of the tree, and the change listeners
  * will be notified.
  * 用于创建存储的'reduce'函数将被当前状态树和给定的'action'调用。
  * 它的返回值将被认为是树的**next**状态,并且会通知更改侦听器。
  *
  * The base implementation only supports plain object actions. If you want to
  * dispatch a Promise, an Observable, a thunk, or something else, you need to
  * wrap your store creating function into the corresponding middleware. For
  * example, see the documentation for the `redux-thunk` package. Even the
  * middleware will eventually dispatch plain object actions using this method.
  * 基本实现只支持普通对象操作。
  * 如果您希望分派一个Promise、一个Observable、一个thunk或其他东西,您需要将store创建函数封装到相应的中间件中。
  * 例如,请参阅“redux-thunk”包的文档。甚至中间件最终也会使用这种方法来调度普通对象操作。
  *
  * @param {Object} action A plain object representing “what changed”. It is
  * a good idea to keep actions serializable so you can record and replay user
  * sessions, or use the time travelling `redux-devtools`. An action must have
  * a `type` property which may not be `undefined`. It is a good idea to use
  * string constants for action types.
  *
  * @returns {Object} For convenience, the same action object you dispatched.
  *
  * Note that, if you use a custom middleware, it may wrap `dispatch()` to
  * return something else (for example, a Promise you can await).
  * 根据传入的action,调用currentReducer对currentState进行修改,并依次调用当前监听队列中的所有监听函数,返回传入的action
  */
 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
 }


 /**
  * Replaces the reducer currently used by the store to calculate the state.
  * 替换存储区当前用于计算状态的reducer。
  *
  * You might need this if your app implements code splitting and you want to
  * load some of the reducers dynamically. You might also need this if you
  * implement a hot reloading mechanism for Redux.
  * 如果您的应用程序实现了代码分割,并且您想动态加载一些reducer,那么您可能需要这个。如果您为Redux实现热重载机制,您可能还需要这个。
  *
  * @param {Function} nextReducer The reducer for the store to use instead.
  * @returns {void}
  */
 function replaceReducer(nextReducer) {
   if (typeof nextReducer !== 'function') {
     throw new Error('Expected the nextReducer to be a function.')
   }


   currentReducer = nextReducer


   // This action has a similiar effect to ActionTypes.INIT.
   // Any reducers that existed in both the new and old rootReducer
   // will receive the previous state. This effectively populates
   // the new state tree with any relevant data from the old one.
   dispatch({ type: ActionTypes.REPLACE })
 }


 /**
  * Interoperability point for observable/reactive libraries.
  * 可观察/反应库的互操作性点。
  *
  * @returns {observable} A minimal observable of state changes.
  * For more information, see the observable proposal:
  * https://github.com/tc39/proposal-observable
  */
 function observable() {
   const outerSubscribe = subscribe
   return {
     /**
      * The minimal observable subscription method.
      * @param {Object} observer Any object that can be used as an observer.
      * The observer object should have a `next` method.
      * @returns {subscription} An object with an `unsubscribe` method that can
      * be used to unsubscribe the observable from the store, and prevent further
      * emission of values from the observable.
      */
     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
     }
   }
 }


 // When a store is created, an "INIT" action is dispatched so that every
 // reducer returns their initial state. This effectively populates
 // the initial state tree.
 dispatch({ type: ActionTypes.INIT })


 return {
   dispatch,
   subscribe,
   getState,
   replaceReducer,
   [$$observable]: observable
 }
}
  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值