美文网首页
Redux源码解析(学习笔记)

Redux源码解析(学习笔记)

作者: 东冬2086 | 来源:发表于2020-10-11 12:34 被阅读0次

    最近比较空闲,就想看点源码,学习记录一下。如果自己收获能给他人些帮助,是一件开心的事也会更有动力。今天的主角是Redux。
    先唠叨几句耳熟能详的话

    Redux 是 JavaScript 状态容器,提供可预测化的状态管理。

    Redux三大原则:

    • 单一数据源
    • State 只能通过触发Action
    • 使用纯函数来执行修改

    Redux的源码很少,5个核心文件。

    QQ截图20201010152216.png

    1. 首先来看createStore.js,一切的开始。

    const store = createStore(reducer, preloadedState, enhancer);
    

    这个函数默认接收三个参数(改变状态的方法,状态的初始值,增强器(增强后的store)),返回的store对象中包含dispatch,getState,subscribe等方法。(笔者只列出在工作中常用的)。
    这个时候,代码应该是这样的

    function createStore(reducer, preloadedState, enhancer) {
      function dispatch() {}
      function getState() {}
      function subscribe() {}
      return {
        dispatch,
        getState,
        subscribe
      };
    }
    

    接下来,就要对传入参数做判断,按规矩办事。这里就直接贴源码了,都是条件判断。


    image.png

    重点注意下,红框中的部分。至于这里为什么这么写,后面细说。不耽误理解createStore这个函数。
    下面将这个三个函数分别简单实现

    1. getState 从简单入手,将初始的state或者改变后的state返回。代码变成这样
    function createStore(reducer, preloadedState, enhancer) {
      let currentState = preloadedState;
      function getState() {
          return currentState;
      }
      return { getState };
    }
    
    1. subscribe 使用发布订阅模式,收集每一个订阅state的函数。
    function createStore(reducer, preloadedState, enhancer) {
      let currentListeners = []
      let nextListeners = currentListeners
      function subscribe(listener) {
        //  ensureCanMutateNextListeners() 这个函数在官方的解释为,进行浅拷贝,防止分发过程中,增加/取消订阅事件,而引发出问题。先去掉
        nextListeners.push(listener)
        return function unsubscribe() {
        // nextListeners.splice(index, 1) 
       }
      }
      return { subscribe };
    
    1. dispatch 在dispatch的时候,会改变state,reducer返回新的state,所以dispatch内部调用reducer。订阅者能收到消息,说明subscribe订阅的函数会执行。
    function createStore(reducer, preloadedState, enhancer) {
      let currentState = preloadedState;
      let currentReducer = reducer;
      let nextListeners = currentListeners
    
      function dispatch(action) {
        currentState = currentReducer(action);
        for (let i = 0; i < nextListeners .length; i++) {
          const listener = listeners[i]
          listener()
        }
        return action;
      }
    // 默认会执行一次 dispatch,简单写
    // dispatch();
      return {
        getState,
        dispatch,
        subscribe
      };
    }
    

    不传入增强器enhancer时,代码大致是这样

    function createStore(reducer, preloadedState, enhancer) {
      let currentState = preloadedState;
      let currentReducer = reducer;
      let currentListeners = []
      let nextListeners = currentListeners
      function getState() {
          return currentState;
      }
      function dispatch(action) {
        currentState = currentReducer(action);
        for (let i = 0; i < nextListeners .length; i++) {
          const listener = listeners[i]
          listener();
        }
        return action;
      }
    
      function subscribe(listener) {
        // ensureCanMutateNextListeners();
        nextListeners.push(listener)
        return function unsubscribe() {}
      }
      return { getState, dispatch, subscribe };
    }
    

    现在,我们来看传入enhancer的情况。这里要用到applyMiddleware.js中的applyMiddleware函数,这个函数返回增强后的store,将dispatch方法增强。让原来一步操作的dispatch(action),变成执行func1->func2->...->store.dispatch(action)。

    // 上面createStore.js  -> enhancer(createStore)(reducer, preloadedState)  => { getState, dispatch, subscribe } 这个是调用applyMiddleware()函数后的返回函数enhancer,enhancer执行2次最后返回对象store,由此推测代码应该是个样子的
    function applyMiddleware(...middlewares) {
      return () => () => ({ getState, dispatch, subscribe })
    }
    

    第一步要有store,才能增强其dispatch方法,获取store的唯一途径是调用createStore函数,将reducer和initialState传入。现在改成这样

    // applyMiddleware(...middlewares) -> enhancer(createStore)(reducer, preloadedState)
    // enhancer(createStore) ->  (...args) => { const store = createStore(...args);  return store; }
    // enhancer(createStore)(reducer, preloadedState) => store;
    function applyMiddleware(...middlewares) {
      return (createStore) => (...args) => {
         const store = createStore(...args);
         return store;
      }
    }
    

    下面,把中间件引进来,我们知道中间件是层层包裹的,经过中间件后,返回的dispatch再调用dispatch时,会把外层函数都执行了,最后再执行dispatch(action)。可能是这样

     function applyMiddleware(...middlewares) {
        return createStore => (...args) => {
          const store = createStore(...args);
          let dispatch = compose(...middlewares)(store.dispatch);
          return { ...store, dispatch };
        }
     }
    

    compose.js ,返回一个函数

    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)));
    }
    

    个人猜测这里中间件如果这样写compose(...middlewares)(store.dispatch),中间件的只能操作dispatch,将这个store整体传入又不安全。如果给每个中间件都注入一个getState,dispatch。就能做更多的事情。代码可能变成这样

     function applyMiddleware(...middlewares) {
        return createStore => (...args) => {
          const store = createStore(...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,
              // 在redux 3.x的版本源码,都是这样的 dispatch: (...args) => (store.dispatch)(...args);  这好理解每个中间件通过作用域链拿到dispatch,接下来就按照3.x的说。
              // 4.x版本的写法,自己没想明白,上面的英文解释说,防止在构建时调用dispatch,我自己还没有领会到精髓,了解的朋友请帮忙指点迷津 
              dispatch: (...args) => dispatch(...args)
          }
          const chain = middlewares.map(middleware => middleware(middlewareAPI))
          let dispatch = compose(...chain)(store.dispatch);
          return { ...store, dispatch };
        }
     }
    

    这样,就可以在每次调用dispatch时,由中间件控制dispatch何时调用。
    最后,再来看一下中间件的写法。({getState, dispatch}) => next => action => {},格式固定。
    以redux-thunk为例,简单说一下。

    // redux-thunk 
    function createThunkMiddleware(extraArgument) {
      return ({ dispatch, getState }) => next => action => {
        if (typeof action === 'function') {
          return action(dispatch, getState, extraArgument);
        }
        return next(action);
      };
    }
    const thunk = createThunkMiddleware();  // 这里执行了一次
    thunk.withExtraArgument = createThunkMiddleware;
    export default thunk;
    

    最简单的使用方式,当参数是函数时,就执行这个函数并将dispatch、getState作为参数传入。

    store.dispatch(
      dispatch => {
        setTimeout( () => {
          dispatch(action)
        },1000)
      }
    );
    

    第一次写博客,有点慌张语言的组织不太好,有些地方还有点啰嗦。如果个人理解偏差,请大家不吝赐教,共同进步。

    相关文章

      网友评论

          本文标题:Redux源码解析(学习笔记)

          本文链接:https://www.haomeiwen.com/subject/fyadpktx.html