coderliush / Blog

博客
0 stars 0 forks source link

react 系列之 redux 源码学习 #2

Open coderliush opened 4 years ago

coderliush commented 4 years ago

redux-thunk

redux action creatos 返回一个对象,dispatch 一个对象。而 redux-thunk 可以使 dispatch 一个函数,这个函数执行后返回一个对象。 使用示例

import { createStore, combineReducers, applyMiddleware } from 'redux'
import thunk from 'redux-thunk'

function asyncFn(forPerson) {
  return function (dispatch) {
    return fetch('https://www.google.com/search?q=secret+sauce')().then(
      sauce => dispatch({ type: 'test', otherValue }),
    )
  }
}

store.dispatch(
  asyncFn('person')
).then(() => {
  console.log('Done!')
})

middleware 都接受 store, 即可获得 dispatch getState 等 store 的方法。 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;
coderliush commented 4 years ago

redux 主要是为了解决组件之间的传值问题,所有的 state 都以一个对象树的形式储存在一个单一的 store 中,子组件可以获取 state 和 修改 state。下面是官网的一个例子:

import { createStore } from 'redux'
function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT':
    return state - 1;
  default:
    return state;
  }
}

// api: createStore(reducer, preloadedState, enhancer)
// return: { dispatch, getState, subscribe ... }
let store = createStore(counter)

// api: dispatch(action)
// dispatch 根据 action 更新 state, 执行 subscribe 注册的箭筒函数列表,所以 subscribe 能获得最新的 state 
store.dispatch({ type: 'INCREMENT' });
// 1
store.dispatch({ type: 'INCREMENT' });
// 2
store.dispatch({ type: 'DECREMENT' });
// 1

// api: subscribe(listener) 
// 讲回调函数 push 到一个列表中。
store.subscribe(() =>
  this.setState({
    newState: store.getState()
  })
);

redux 概念比较多,如果不了解内部实现,一段时间不用就模糊了。redux 源码不太多,抽时间学习总结一下。 源码入口从 index.js 开始,下文代码来自 redux 源码,删除了一些验证判断,详细见 redux

export {
  createStore,
  combineReducers,
  bindActionCreators,
  applyMiddleware,
  compose,
  __DO_NOT_USE__ActionTypes
}

createStore

export default function createStore(reducer, preloadedState, enhancer) {
  //  createStore 第二个参数初始 state 可省略。
  if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
    enhancer = preloadedState
    preloadedState = undefined
  }
  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false

  return {
    dispatch,
    subscribe,
    getState,
    replaceReducer,
    [$$observable]: observable
  }

在 redux 中,如果 state 是全局变量,那么很容易被意外修改。所以 createStore 接收初始的 state,保存在函数内部,外部只能通过 getState 获取。 createStore 接收 reducer、initState 和 enhancer 3 个参数。 reducer 函数接收 state 和 action,根据 action.type 返回新的 state:

function reducer(state, action) {
    switch(action.type) {
        case 'type1':
            return {
                ...state,
                type
            }
        default:
            return state;
    }
}

,根据外部传来的 reducer 修改 state,initState 设置初始 state,enhancer 加强 dispatch 的功能。 createStore 导出 dispatch,subscribe 等常用的方法。

dispatch 函数代码如下:

  function dispatch(action) {
    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
  }

dispatch函数接收一个 action,做了以下几件事情:

  1. 执行 reducer 函数,返回新的 state。
  2. 执行所有的 listener,listener 是 subscribe 添加进去的回调函数,( 见下文 subscribe )
  3. 返回传入的 action。( action 可供其它的 middleware 使用,见下文 middleware ) 在 createStore 中有这样一句:
    // 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 })

    这里的意思是,没有匹配的 type,reducer 返回初始 state。

subscribe 函数代码如下:

  function subscribe(listener) {

    let isSubscribed = true

    ensureCanMutateNextListeners()
    nextListeners.push(listener)

    return function unsubscribe() {
      isSubscribed = false

      ensureCanMutateNextListeners()
      const index = nextListeners.indexOf(listener)
      nextListeners.splice(index, 1)
    }
  }

subscribe函数接收一个 listener 回调函数,将 listener push 到 nextListeners 中,返回 unsubscribe 函数,将这个回调函数卸载。其中 ensureCanMutateNextListeners 返回 currentListeners 浅拷贝的一个数组,操作新的数组不改变原始数组。

  function ensureCanMutateNextListeners() {
    if (nextListeners === currentListeners) {
      nextListeners = currentListeners.slice()
    }
  }

getState 函数返回 state

  function getState() {
    return currentState
  }

为了便于理解和总结,附 createStore 的实现,原文链接刘小夕小姐姐的 从零开始实现Redux

function createStore(reducer) {
    let state;
    let listeners = [];
    const getState = () => state;
    //subscribe 每次调用,都会返回一个取消订阅的方法
    const subscribe = (ln) => { 
        listeners.push(ln);
        //订阅之后,也要允许取消订阅。
        //难道我订了某本杂志之后,就不允许我退订吗?可怕~
        const unsubscribe = () => {
            listeners = listeners.filter(listener => ln !== listener);
        }
        return unsubscribe;
    };
    const dispatch = (action) => {
        //reducer(state, action) 返回一个新状态
        state = reducer(state, action);
        listeners.forEach(ln => ln());

    }
    //你要是有个 action 的 type 的值正好和 `@@redux/__INIT__${Math.random()}` 相等,我敬你是个狠人
    dispatch({ type: `@@redux/__INIT__${Math.random()}` });

    return {
        getState,
        dispatch,
        subscribe
    }
}

总结:createStore 函数接收 reducer,preloadedState, enhancer 函数,返回我们常用的 dispatch,subscribe,getState 等。其中 dispatch 接收一个 action,做了这几件事情:1. 运行了 reducer 函数,返回了新的 state。2. 运行了 listeners 队列中所有的回调。3. 返回 action,返回的 action 可以供下文使用。

combineReducer

在实际项目中,随着应用的开发,我们可能有一个很大的 reducer,一般情况下我们都是对每一个模块编写对应的 reducer,最后合成一个 reducer 。combineReducer 做的就是这件事

applyMiddleware

Middleware 作用是包装 store 的 dispatch 方法来达到想要的目的。 有时候我们需要在 dispatch 之前做一些事情,比如常见的打印一个 log:

<button onClick={() => {
    ...  // middleware1 do something
    store.dispatch(action)
}}>
</botton>

如果很多事件都需要加,我们不可能每次地方都加。很自然的我们想到可以放在 dispatch 函数里面,所以重写 dispatch 函数:

let next = store.dispatch
store.dispatch = () => {
    ...  // middleware1 do something
    next(action)
}

middleware 的概念就是在 dispatch 前后做一些事情,增强 dispatch 的功能。 如果我们还有其它 middleware, 比如:

let next = store.dispatch
store.dispatch = () => {
    ...  // middleware2 do something
    next(action)
}

我们想到可以在第一个 middleware 中在 dispatch 函数加入自身的功能,返回修改后的 dispatch,传给第二个 middleware。每一个 middleware 都返回修改后的 dispatch 给下一个使用,合并之后类似于这样:

let next = store.dispatch
store.dispatch = () => {
    ...  // middleware1 do something
    ...  // middleware2 do something 
    return next(action) 
}

applyMiddleware 的一个使用实例

import { createStore, applyMiddleware } from 'redux'
import todos from './reducers'
// 中间件可以获得 store,从而获得 store 的各种方法供自己使用。 logger 返回 '增强的功能' + dispatch 
function logger({ getState }) {
  return (next) => (action) => {
    console.log('will dispatch', action)

    // 调用 middleware 链中下一个 middleware 的 dispatch。
    let returnValue = next(action)

    console.log('state after dispatch', getState())

    // 一般会是 action 本身,除非
    // 后面的 middleware 修改了它。
    return returnValue
  }
}

let store = createStore(
  todos,
  [ 'Use Redux' ],
  applyMiddleware(logger)    // enhancer === applyMiddleware( ...middlewares ),enhancer = applyMiddleware() = function
)

store.dispatch({
  type: 'ADD_TODO',
  text: 'Understand the middleware'
})
// (将打印如下信息:)
// will dispatch: { type: 'ADD_TODO', text: 'Understand the middleware' }
// state after dispatch: [ 'Use Redux', 'Understand the middleware' ]

而 redux 中的 applyMiddleware 就是接收所有的 middleware,applyMiddleware( middleware1, middleware2, middleware3 )。

export default function createStore(reducer, preloadedState, enhancer) {
    ...
    if (typeof enhancer !== 'undefined') {
        if (typeof enhancer !== 'function') {
            throw new Error('Expected the enhancer to be a function.')
        }

        return enhancer(createStore)(reducer, preloadedState)   //  applyMiddleware( ...middleware )( createStore )( reducer, preloadedState )
    }
    ...
}
 // @param {...Function} middlewares The middleware chain to be applied.
 // @returns {Function}  applyMiddleware() 返回一个函数
export default function applyMiddleware(...middlewares) {
  // createStore: enhancer 传入的第一个参数。
  // args = [reducer, preloadedState]
  return createStore => (...args) => {
    const store = createStore(...args)
    const middlewareAPI = {
      getState: store.getState,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))   
    // compose 第一个参数是所有的中间件函数。第二个参数是每个中间件函数执行的参数
    // 对每一个中间件,从右往左执行 fn( store.dispatch ),返回值作为下一个函数的参数
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

compose

function compose(...funcs) {
    //如果没有中间件
    if (funcs.length === 0) {
        return arg => arg
    }
    //中间件长度为1
    if (funcs.length === 1) {
        return funcs[0]
    }

    return funcs.reduce((prev, current) => (...args) => prev(current(...args)));
}