Redux源码解析(学习笔记)

2020-10-11  本文已影响0人  东冬2086

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

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

Redux三大原则:

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

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

上一篇 下一篇

猜你喜欢

热点阅读