Redux源码浅析

1,069 阅读8分钟

使用redux三大原则

  • 单一数据源,整个应用只能有一个state
  • state只读,改变state只能通过dispatch触发一个action
  • 通过reducer纯函数去改变state

redux的方法

  • createStore 创建一个store,返回一个对象包含 dispatchsubscribegetState等这些方法

  • applyMiddleware 接受多个中间件,我们在dispatch(action)时一层一层的经过中间件

  • compose 合并中间件,compose(middlerware1, middlerware2, middlerware3) 最终返回 compose(middlerware1(middlerware2(middlerware3(...args))))

  • bindActionCreatordispatchaction 进行绑定

  • combineReducers 接收 reducers 循环对state进行处理


createStore方法

createStore方法相对简单,接收三个参数 reducerpreloadedStateenhancer返回一个对象,下面是redux的基本使用

import {createStore} from 'redux';
// reduce 纯函数
const testReduce = (initState = 777, action) => {
    switch (action.type) {
        case 'add':
            return initState += action.payload;
        case 'minus':
            return initState -= action.payload;
        default:
            return initState;
    }
};
// 创建action的function
const createAction = (type, num) => {
    return {
        type,
        payload: num
    }
}

const store = createStore(testReduce);
console.log('store.getState()', store.getState());// 初始state 777
// subscribe返回一个去消监听的方法
store.subscribe(function test1() {
    console.log('store.getState() test1', store.getState());
})()
store.subscribe(function test2() {
    console.log('store.getState() test2', store.getState()); // 780 778
})
store.dispatch(createAction('add', 3)); // 触发add的action
store.dispatch(createAction('minus', 2)); // 触发minus的action

我们来看下createStore的源码

export default function createStore(reducer, preloadedState, enhancer) {
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    // 这里是判断把enhancer当成preloadedState传入的
    // 也就是 createStore(reduce, enhancer) 这样去调用
    enhancer = preloadedState;
  }
  if (enhancer) {
    enhancer(createStore)(reducer, preloadedState)
  }

  let currentReducer = reducer // 传入的reducer纯函数
  let currentState = preloadedState // 传入的默认state
  let currentListeners = [] // 初始化监听器数组
  let nextListeners = currentListeners // 临时的监听器数组
  let isDispatching = false // 是否正在调用dispatch过程中

  // 把当前的state返回去
  function getState() {
    // 判断是否dispatch过并且是不是在dispatch过程中
    if (isDispatching) {... }
    return currentState
  }
  // 添加listenser, 返回一个取消listener的函数
  function subscribe(listener) {
    if (typeof listener !== 'function') {... }

    if (isDispatching) {... }

    let isSubscribed = true

    nextListeners = currentListeners.slice()
    nextListeners.push(listener)

    return function unsubscribe() {
      if (!isSubscribed) {
        return
      }

      if (isDispatching) {... }

      isSubscribed = false
      // 把监听器从数组移除
      nextListeners = currentListeners.slice()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

  function dispatch(action) {
    // 判断action必须是object, 并且必须有type的字段
    if (!isPlainObject(action)) {...}
    if (typeof action.type === 'undefined') {... }

    if (isDispatching) {... }

    try {
      isDispatching = true
      // 把currentState和action传给reduce得到新的currentState
      currentState = currentReducer(currentState, action)
    } finally {
      isDispatching = false
    }

    const listeners = (currentListeners = nextListeners)
    // 循环listeners,这就是为什么dispatch(action)会触发listeners
    // 触发dispatch(action) 调用subscribe添加的callback
    for (let i = 0; i < listeners.length; i++) {
      const listener = listeners[i]
      listener()
    }
    // 返回action
    return action
  }
  // 触发一个唯一的action, 触发reduce里的default选项,得到初始state
  // 这里是调用玩createStore后会得到一个初始的 state
  dispatch({ type: Math.radom() })
  return { getState, dispatch, subscribe }
}

createStore方法相对简单,里面比较简单的用到了我们的订阅/发布模式


applyMiddleware 方法

上面我们调用的action都是同步的,在开发过程中我们避免不了使用异步开发,怎么去使用异步的action?这个时候就用到了我们的中间件,我们先来看怎么使用一个中间件:

// 中间件是一个高阶函数
// const AsyncThunk = ({getState, dispatch}) => next => action => {
//    if(typeof action === 'function') {
//        return action(dispatch, getState)
//    }
//    return next(action)
// }

//现转换成普通的函数,这样方便理解 
const AsyncThunk = function({getState, dispatch}) {
    return function(next) {
        return function(action) {
            if(typeof action === 'function') {
                return action(dispatch, getState)
            }
            return next(action)
        }
    }
}
// 把异步中间件传进去
const store = createStore(testReduce, applyMiddleware(AsyncThunk));
// 异步action, 
const asyncAdd = () => dispatch => {
    setTimeout(() => {
        dispatch(computeAction('add', 7))// 两秒后打印785
    }, 2000)
}
store.dispatch(asyncAdd())

上面是中间件的使用和异步中间件的主要源码,我们结合着applyMiddlewarecreateStore进行阅读

const store = createStore(
    testReduce, 
    // applyMiddleware 第一次调用
    applyMiddleware(AsyncThunk)
);

// createStore.js
export default createStore(reducers, preloadState, enhancer) {
    ...
    if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
        enhancer = preloadedState
        preloadedState = undefined
    }

    if (typeof enhancer !== 'undefined') {
        // 调用enhancer 传入createStore, 返回一个接收reducer和preloadedState的func并调用
        // applyMiddleware第二第三次调用
        return enhancer(createStore)(reducer, preloadedState)
    }
    ...
}

// applyMiddleware部分源码
export default function applyMiddleware(...middlewares) {
    // createStore就是上面的enhancer第二次调用传的createStore函数
    // ...args 就是第三次调用传进来的reducers和preloadedState
    return function (createStore) {
        return function (...args) {
            // 创建store
            const store = createStore(...args)
            let dispatch = () => {...}
            const middlewareAPI = {
                getState: store.getState,
                dispatch: function(...args) {
                    // 这个时候的dispatch相当于是下面已经包装过的dispatch
                    return dispatch(...args)
                }
            }
            if (middlewares.length === 1) {
                // 这里的dispatch是中间件被调用了两次返回了一个接收action的函数
                dispatch = middlewares[0](middlewareAPI)(store.dispatch)
            }
            return {
                ...store,
                dispatch
            }
        }
    }
}
const asyncAdd = () => dispatch => {
    setTimeout(() => {
        dispatch(computeAction('add', 7))// 两秒后打印785
    }, 2000)
}
store.dispatch(asyncAdd())

在调用这个store.dispatch的时候就等同于middlewares[0](middlewareAPI)(store.dispatch)(asyncAdd())

  • 而中间件接收到action,判断是个函数类型,就把包装过的dispatchgetState方法传给了action的函数也就是asyncAdd第一次调用后返回的函数;
  • 如果正常的action经过了这个中间件则会执行next(action) = dispatch(action)这个dispatch是没有被包装过的

compose方法

我们在使用中间件的时候可能会用到很多中间件,这个时候我们就需要用到compose高阶函数,合并compose(middlerware1, middlerware2, middlerware3) 最终返回 compose(middlerware1(middlerware2(middlerware3(...args))))

export default function compose(...funcs) {
    // 传入的中间件length是0, 返回一个function,第二次调用传入store.dispatch返回store.dispatch
    if (funcs.length === 0) {
        return arg => arg
    }
    // 传入的中间件length是0,就把这个中间件返回去
    if (funcs.length === 1) {
        return funcs[0]
    }
    // 第一次 b中间件接受的参数是dispatch, a中间件next参数就是b调用完返回的接收action的函数
    // 这样在组件里调用的时候会 经过 a的 action => {} 
    // 然后到最后一个的时候用 next(action) = dispatch(action)去改变state
    return funcs.reduce(function(a, b) {
        return function(...args) {
            return a(b(...args))
        }
    })
}
// applyMiddleware也将改成
...
const chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)
// compose(middlerware1(middlerware2(middlerware3(...args))))
...
// 创建store
const store = createStore(reducers, applyMiddleware(middlerware1, middlerware2, middlerware3))

这里也就是说我们compose传入的store.dispatch给了middleware3,此时middleware3返回的是一个接收action的函数传给了middleware2next,以此类推我们当前中间件的next参数是上一个中间件的function (action){},当没有上一个中间件的时候,next参数是没有包装过的dispatch函数,也就是说我们在执行包装过的dispatch的时候

  • 调用了middleware1function (action){}并执行了next(action),而middleware1nextmiddleware2function (action){}
  • middleware3的时候next是没有包装过的dispatchnext(action) = dispatch(action) 这样最初的action这样一层一层经过中间件到达我们真正的dispatch手里改变状态 看到这里可能会有些晕头转向的,我们看个demo:
// 区分是否经过包装的dispatch
const store_dispatch = function store_dispatch(action) {
    console.log('action', action);
}
const middleware1 = next => action => {
    console.log(`function name: middleware1\n dispatch: ${next}\n args: ${JSON.stringify(action)}\n`)
    console.log('next.name:'+next.name, '       ','store_dispatch.name:'+store_dispatch.name);
    console.log('\n\n');
    return next(action)
}
const middleware2 = next => action => {
    console.log(`function name: middleware2\n dispatch: ${next}\n action: ${JSON.stringify(action)}\n`)
    console.log('next.name:'+next.name, '       ','store_dispatch.name:'+store_dispatch.name);
    console.log('\n\n');
    return next(action)
}
const middleware3 = next => action => {
    console.log(`function name: middleware3\n dispatch: ${next}\n action: ${JSON.stringify(action)}\n`)
    console.log('next.name:'+next.name, '       ','store_dispatch.name:'+store_dispatch.name);
    console.log('\n\n');
    return next(action)
}
// 包装过的dispatch
const dispatch = compose(middleware1, middleware2, middleware3)(store_dispatch);

dispatch('123')

从上图我们不难看出来执行顺序是middleware1->middleware2->middleware3,middleware1nextmiddleware2的返回值


combineReducers方法

在我们状态很多的情况下,我们不可能也不想把所有的状态都通过一个reducer去改变,这个时候我们就用到了combineReducers

// reduce 纯函数, 通过纯函数去改变state
const happinessReducer = (initState = 0, action) => {
    switch (action.type) {
        case 'add_happiness':
            return initState += action.payload;
        default:
            return initState;
    }
};
// reduce 纯函数, 通过纯函数去改变state
const badThingReducer = (initState = 10, action) => {
    switch (action.type) {
        case 'minus_bad_things':
            return initState -= action.payload;
        default:
            return initState;
    }
};

const store = createStore(combineReducers({
    happiness: happinessReducer,
    badThings: badThingReducer
}));

console.log(store);
console.log('store.getState()', store.getState());

store.subscribe(function test2() {
    console.log('store.getState() test2', store.getState());
})
store.dispatch({
    type: 'add_happiness',
    payload: 99999
});
store.dispatch({
    type: 'minus_bad_things',
    payload: 10
});

上面试是简单的使用方法,接下来我们来看源码:

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  // 把传过来的reducers进行了一次拷贝
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]
    if (typeof reducers[key] === 'function') {
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  return function combination(state = {}, action) {
    // 默认是状态没有改变的
    let hasChanged = false
    const nextState = {}
    // 循环所有的reducer拿到当前的状态和改变后的状态
    for (let i = 0; i < finalReducerKeys.length; i++) {
      const key = finalReducerKeys[i]
      const reducer = finalReducers[key]
      const previousStateForKey = state[key]
      const nextStateForKey = reducer(previousStateForKey, action)
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    // 如果改变了就返回最新的nextState否则就返回state
    return hasChanged ? nextState : state
  }
}

combineReducers函数源码还是比较容易看懂的,就不多bb了。


bindActionCreators方法

我们在react中使用redux时,不想每个组件都引入dispatch去调用action,这个时候就用到了bindActionCreators,先看下基本使用:

...
// action 一个动作
const computeAction = (type, num) => {
    return {
        type,
        payload: num
    }
}

const store = createStore(testReduce);

console.log(store);
console.log('store.getState()', store.getState());

store.subscribe(function test2() {
    console.log('store.getState() test2', store.getState());
})
const actionCreators = {
    computeAction
}
// 传入一个actionCreators,和dispatch
const bindDispatch = bindActionCreators(actionCreators, store.dispatch);
console.log(bindDispatch)
// 直接调用bindDispatch上的方法就好了
bindDispatch.computeAction('add', 3);
bindDispatch.computeAction('minus', 2);

接下来我们看下源码:

function bindActionCreator(actionCreator, dispatch) {
    // 返回一个function,把参数透传给传过来的actionCreator函数
    return function() {
      return dispatch(actionCreator.apply(this, arguments))
    }
  }
  export default function bindActionCreators(actionCreators, dispatch) {
    // 传的actionCreators是个函数,直接去绑定dispatch
    if (typeof actionCreators === 'function') {
      return bindActionCreator(actionCreators, dispatch)
    }
  
    const boundActionCreators = {}
    // 是对象,循环key,给每个actionCreator绑定dispatch
    for (const key in actionCreators) {
      const actionCreator = actionCreators[key]
      if (typeof actionCreator === 'function') {
        boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
      }
    }
    return boundActionCreators
  }
  

bindActionCreators主要应用场景在react-redux中,进行connect的时候让react无感绑定dispatch,不用每个组件都引入dispatch去调用。


结语

react-redux源码解读之后也会写出来希望能帮助到大家,上面很多代码自己看的时候能够理解,但是不知道如何用语言组织出比较能够让大家理解的话来帮助大家去吃透redux,另外redux只是一个状态管理的库,可以用来用在任何框架上,不要主观的认为只能用在react上,希望可以帮助到大家,Thanks。