最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • redux简单实现(包含redux中核心api)

    正文概述 掘金(him8)   2021-02-14   710

    我们先来看一下redux中暴露的api;

    核心方法    ( [ ] 代表参数可选 )

    • createStore(reducer, [preloadedState], [enhancer])
    • compose(...functions)
    • applyMiddleware(...middlewares)
    • combineReducers(reducers)
    • bindActionCreators(actionCreators, dispatch)

    Store API

    • getState()
    • dispatch(action)
    • subscribe(listener)
    • getReducer()
    • replaceReducer(nextReducer)


    一:createStore(reducer, [preloadedState], [enhancer])

    redux的设计基于flux思想,数据流为单向,改变state的唯一途径就是通过dipatch派发action

    • 基于发布订阅模式,实现下代码
    const createStore = (reducer, preloadedState, enhancer)=>{
        /* 处理下 createStore(reducer, enhancer) 调用情况*/
        if(typeof preloadedState === "function" && !enhancer){
            enhancer = preloadedState;
            preloadedState = undefined;
        }
        /* 存在中间件传参 */
        if(enhancer && typeof enhancer === "function"){
            /* 执行中间件逻辑,增强dispatch,返回新的store */
            return enhancer(createStore)(reducer, preloadedState)
        }
    
        let state = preloadedState;
        let listeners = [];
    
        const getState = ()=> state;
    
        const dispatch=(action)=>{
            state = reducer(state,action);
             /* 事件派发 */
            listeners.forEach((fn)=>{
                typeof fn === "function" && fn();
            });
           return action
        }
    
        const subscribe = (listener)=>{
            /* 事件订阅 */
            listeners.push(listener); 
            /* 返回取消订阅函数 */
            return ()=>{
                listeners = listeners.filter(fn=> fn !== listener);
            }
        }
    
        const replaceReducer = (nextReducer)=>{
            if(typeof nextReducer === "function"){
                reducer = nextReducer;
                dispatch({})
            }
        }
        /* 手动初始state  */
        dispatch({}); 
    
        return {
            getState,
            dispatch,
            subscribe,
            replaceReducer
        }
    }
    

    上面的代码写完了,但需要再抠一些细节, 考虑下下面两个问题:

    1. dispatch方法使用时:代码state = reducer(state,action);如果reducer调用中又存在dispatch派发怎么办?
    2. dispatch中遍历执行fn(),如果fn执行时增加了新的订阅subscribe,或者取消了某个订阅又怎么办?

    下面看下真实的redux源码中是怎么处理的

    export default function createStore<
      S,
      A extends Action,
      Ext = {},
      StateExt = never
    >(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S>,
      enhancer?: StoreEnhancer<Ext, StateExt>
    ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
    export default function createStore<
      S,
      A extends Action,
      Ext = {},
      StateExt = never
    >(
      reducer: Reducer<S, A>,
      preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,
      enhancer?: StoreEnhancer<Ext, StateExt>
    ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {
      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 as StoreEnhancer<Ext, StateExt>
        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 as PreloadedState<S>
        ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
      }
    
      if (typeof reducer !== 'function') {
        throw new Error('Expected the reducer to be a function.')
      }
    
      let currentReducer = reducer
      let currentState = preloadedState as S
      let currentListeners: (() => void)[] | null = []
      let nextListeners = currentListeners
      let isDispatching = false
    
      /**
       * This makes a shallow copy of currentListeners so we can use
       * nextListeners as a temporary list while dispatching.
       *
       * 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 The current state tree of your application.
       */
      function getState(): S {
        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 as S
      }
    
      /**
       * 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.
       *
       * 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 listener A callback to be invoked on every dispatch.
       * @returns A function to remove this change listener.
       */
      function subscribe(listener: () => void) {
        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/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/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.
       *
       * 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.
       *
       * @param 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 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).
       */
      function dispatch(action: A) {
        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.
       *
       * 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.
       *
       * @param nextReducer The reducer for the store to use instead.
       * @returns The same store instance with a new reducer in place.
       */
      function replaceReducer<NewState, NewActions extends A>(
        nextReducer: Reducer<NewState, NewActions>
      ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {
        if (typeof nextReducer !== 'function') {
          throw new Error('Expected the nextReducer to be a function.')
        }
    
        // TODO: do this more elegantly
        ;((currentReducer as unknown) as Reducer<
          NewState,
          NewActions
        >) = nextReducer
    
        // This action has a similar 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 } as A)
        // change the type of the store by casting it to the new store
        return (store as unknown) as Store<
          ExtendState<NewState, StateExt>,
          NewActions,
          StateExt,
          Ext
        > &
          Ext
      }
    
      /**
       * Interoperability point for observable/reactive libraries.
       * @returns 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 observer Any object that can be used as an observer.
           * The observer object should have a `next` method.
           * @returns 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: unknown) {
            if (typeof observer !== 'object' || observer === null) {
              throw new TypeError('Expected the observer to be an object.')
            }
    
            function observeState() {
              const observerAsObserver = observer as Observer<S>
              if (observerAsObserver.next) {
                observerAsObserver.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 } as A)
    
      const store = ({
        dispatch: dispatch as Dispatch<A>,
        subscribe,
        getState,
        replaceReducer,
        [$$observable]: observable
      } as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext
      return store
    }
    

    上面源码中可以看到:

    1. 对于问题1:增加isDispatching字段控制,简称:加锁
    • 代码片段(只截取核心部分,其他地方也有用到isDispatching)
     if (isDispatching) {
        throw new Error('Reducers may not dispatch actions.')
    }
     try {
        isDispatching = true
        currentState = currentReducer(currentState, action)
      } finally {
        isDispatching = false
    }
    
    1. 对于问题2:增加了nextListeners字段
    • 代码片段
    /* 每次改变nextListeners前先进行拷贝 */
    function ensureCanMutateNextListeners() {
      if (nextListeners === currentListeners) {
        nextListeners = currentListeners.slice()
      }
    }
    
    /* subscribe */
    ensureCanMutateNextListeners()
    nextListeners.push(listener)
    
    /* unsubscribe */
    ensureCanMutateNextListeners()
    const index = nextListeners.indexOf(listener)
    nextListeners.splice(index, 1)
    
    /* 真正调用时 */
    const listeners = (currentListeners = nextListeners);
    //  就算listeners中存在方法,执行时进行订阅与取消订阅,我都用原来的listeners
    

    额外: 看看下面的代码分别输出一样嘛

    • 例1
    var list = [0,1,2];
    list.forEach((item)=>{
      list.push(3);
      console.log(item)
    });
    
    • 例2
    var list = [0,1,2];
    for(let i=0;i<list.length;i++){
      if(i===0){
        list.push(3)
      }
      console.log(list[i])
    }
    

    可以看到,有意思的现象,forEach中其实存在一个闭包function环境,
    所以使用listeners.forEach(fn=> {fn()})
    一定程度上避免了问题2的发生

    二:compose(...functions)

    compose是什么函数,compose可以理解为一个柯里化过程,将函数层层执行并最终返回

    • 从右至左调用,比如: compose(f, g, h) 将会返回一个新函数: (...args) => f(g(h(...args))).
    • 基于数组reduce方法实现下代码
    const 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)));   // 这句比较难理解,多读几遍
    }
    

    三:applyMiddleware(...middlewares)

    最核心的中间件代码,看之前,我们先来看一端redux-chunk中间件源码:

    function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => {
        return next=>{
          return action => {
            if (typeof action === 'function') {
               return action(dispatch, getState, extraArgument);
            }
            return next(action);
          }
        }
      };
    }
    const thunk = createThunkMiddleware();
    thunk.withExtraArgument = createThunkMiddleware;
    export default thunk;
    

    上面的redux-thunk 源码可以看到需要顶层提供dispatch, getState参数

    • applyMiddleware实现
    const applyMiddleware=(...middlewares)=>{
    /* 下面是createStore中enhancer */
      return (createStore)=>{
        return (reducer, preloadedState)=>{
          const store = createStore(reducer, preloadedState);
          let dispatch =  ()=>{}
          const middlewareApi = {
            getState: store.getState,
            dispatch: (...args)=> { dispatch(...args) } 
          }
          const chain = middlewares.map(middleware => middleware(middlewareApi));
          dispatch = compose(...chain)(store.dispatch);  // dispatch增强
          return{
            ...store,
            dispatch
          }
        }
      }
    }
    
    • 思考:上面定义时dipatch只是()=> {},作为形参传入到redux-thunk中可以吗?
    • 提示

    四:combineReducers(reducers)

    将{} 对象形式合成最终的大reducer函数形式

    const combineReducers(reducers)=>{
      return (state, action)=>{
        return Object.keys(reducers).reduce((now,item)=>{
          now[item] = reducers[item](state[item], action);
          return now;
        }, {})
      }
    }
    

    五:bindActionCreators(actionCreators, dispatch)

    一般用不到,在react-redux源码中mapDispatchToProps可以为对象时,使用bindActionCreators进行加工处理

    const bindActionCreator = (actionCreator, dispatch)=>{
      return (...args)=>{
        return dispatch(actionCreator(...args))
      }
    }
    
    const bindActionCreators=(actionCreators,dispatch)=>{
      return Object.keys(actions).reduce((now,item)=>{
        now[item]= bindActionCreator(actionCreators[item], dispatch)
        // 等同 now[item]= (...args)=> dispatch(actionCreators[item](...args))
        return now
      },{})
    }
    

    起源地下载网 » redux简单实现(包含redux中核心api)

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元