存在的问题以及redux的解决办法。

多组件共享状态,都要靠父组件一层层往下传,传递复杂

redux,将所有状态都存储到一个仓库中,由一个保安(reducer)进行管理。

状态变更时候需要注意的点

改变状态必须通过dispatch是因为改变状态的时候,可以通知订阅(subscribe)的组件。reducer中不能改变状态是因为遵循纯函数的规则,这样的话可以进行数据重放,时间旅行,利于debug。

只有一个reducer来维护

  1. redux规定只有一个仓库(store),一个保安(reducer)。这样存在的问题就是这个保安要维护的操作就会很多。我们不可能所有组件的操作都到这一个保安函数中去修改。

所以combineReducers就出现了。我们将其分拆成多个 reducer,拆分之后的 reducer 都是相同的结构(state, action),并且每个函数独立负责管理该特定切片 state 的更新。多个拆分之后的 reducer 可以响应一个 action,在需要的情况下独立的更新他们自己的切片 state,最后组合成新的 state。

react-redux是什么

react-redux是个连接库,用来连接仓库和组件。也用来帮我们做所有组件都要做的重复的事情,比如组件订阅仓库变化而做出响应,组件销毁时取消订阅(不取消会一直调用而报错),帮我们做comebineReducer那一步,生成一个大的reducer。

为什么需要mapStateToProps这个函数

因为整个仓库的数据太大了,可能一个组件用不到那么多,所以这个函数相当于过滤的功能,你想要仓库里的什么状态,就返回什么状态。相当于mobx里面的inject。第二个原因就是也可能需要对仓库中的数据进行修改,才是页面中所需要使用的。

mapDispatchToProps

connect函数的第二个参数,可以为对象或者函数。这个参数的作用是将actions挂载到props中,方便组件调用。如果是函数,参数为dispatch,返回一个对象,对象里分别是actions,要自己做dispatch。所以一般直接传入一个对象,对象的名为action名称,值为action creater(也就是返回一个由type的对象),由connect函数中统一处理重写函数,将该对象传入dispatch方法统一调用。

createStore

创建store,提供了订阅变更,派发变更,获取当前仓库状态三个方法。

export default function createStore (reducer, preloadedState) {
  let currentState = preloadedState
  let listeners = []
  function getState () {
    return currentState
  }
  function dispatch (action) {
    currentState = reducer(currentState, action)
    listeners.forEach((listener) => listener())
  }

  function subscribe (listener) {
    listeners.push(listener)
    return function () {
      listeners.filter(item => item !== listener)
    }
  }
  dispatch({
    type: '@@INIT'
  })
  return {
    dispatch,
    subscribe,
    getState
  }
}

combineReducer的意义就是将reducer进行分离。统一由combineReducers进行组织。
每次在执行action的时候,其实会执行每个reducer。为什么要执行所有reducer呢?
因为保安队长也不知道通过dispatch派发过来的是谁。所以要执行下面的所有reduser

// combineReducer的意义就是将reducer进行分离。统一由combineReducers进行组织。
// 每次在执行action的时候,其实会执行每个reducer。为什么要执行所有reducer呢?
// 因为保安队长也不知道通过dispatch派发过来的是谁。所以要执行下面的所有reduser
export default function combineReducers (reducersObj) {
  if (typeof reducersObj !== 'object' || reducersObj === null) {
    throw Error('what is your problem?')
  }
  // 进行浅拷贝,不影响原有对象
  let finalReducers = {}
  Object.keys(reducersObj).forEach((key) => {
    const reducer = reducersObj[key]
    if (typeof reducer === 'function') {
      finalReducers[key] = reducersObj[key]
    }
  })
  return function (previewState = {}, action) { // 原来是在这里赋默认值。
    let nextState = {}
    let hasChanged = false

    Object.keys(finalReducers).forEach((key) => {
      const prevState = previewState[key]
      const reducer = finalReducers[key]
      nextState[key] = reducer(prevState, action)
      if (nextState[key] !== prevState) {
        hasChanged = true
      }
    })
    return hasChanged ? nextState : previewState
  }
}

bindActionCreators

该方法就是为了减少dispatch这个模版代码的使用,统一在这里调用。

function bindActionCreator (actionCreator, dispatch) {
  return function () {
    return dispatch(actionCreator.apply(this, arguments))
  }
}

// 该方法就是为了减少dispatch这个模版代码的使用,统一在这里调用。
export default function bindActionCreators (actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }
  if (typeof actionCreators !== 'object' &&  actionCreators === null) {
    throw Error('what is your problem?') 
  }
  const boundActionCreators = {}
  Object.keys(actionCreators).forEach((key) => {
    boundActionCreators[key] = bindActionCreator(actionCreators[key], dispatch)
  })
  return boundActionCreators
}

compose.js

感觉在js内为了解决回调的问题,都会用到相似的技术,比如koa、express、promise。

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

中间件


中间件的目的是为了执行reducer之前增加一些逻辑。避免直接修改ruducer或者完全重写dispatch,这样dispatch会变得很臃肿。

applyMiddleware.js

import compose from './compose'
// 这个方法出现的目的是为了处理多个中间件的情况。
export default 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,
      dispatch: (...args) => dispatch(...args)
    }
    const chain = middlewares.map(middleware => middleware(middlewareAPI))
    // dispatch = compose(...chain)(store.dispatch)
    let c = compose(...chain)
    console.log(c) // (...args) => a(b(...args)) 得到一个函数(...args) => a(b(...args))
    dispatch = c(store.dispatch) // 先执行b,将store.dispatch传入,此时,最后一个中间件的返回值(也就是中间件最里层的那个参数为action的函数)作为了a的参数。
    // 一层层执行,这个时候就返回了一个大的函数dispatch。这个函数就是第一个中间件的最里层的参数为action的函数。
    // dispatch还是个函数。这样和koa一样是个洋葱模型了。
    // 所以也造成的结果就是,如果在中间件内部不调用next,后面的中间件就不会执行,action也不会被派发。
    // 总结来说,写在前面的中间件最里层的函数先执行,不过在diaoyongnext的时候,会执行下一个中间件最内层的函数。
    // 直到最后一个中间件执行。这时候就执行了dispatch(action)这时候再一层一层往回执行。像洋葱一样。
    return {
      ...store,
      dispatch // !!!! dispatch还是个函数。
    }
  }
}

redux-thunk中间件

// 中间件干的事就是判断当前的action是否对应当前中间件的条件,如果是,就对该action进行处理。
export default function reduxPromise ({dispatch, setState}) {
  return (next) => {
    return (action) => {
      const payload = action.payload
      if (!!payload && (typeof payload === 'function' || typeof payload === 'object') && typeof payload.then === 'function') {
        return payload.then((result) => {
          dispatch({...action, payload: result})
        }).catch((err) => {
          dispatch({...action, payload: err, error: true})
          debugger
          return Promise.reject(err)
        })
      } else {
        next(action)
      }
    }
  }
}

redux-promise中间件

function createThunkMiddleware (extraArgument) {
  return function ({getState, dispatch}) {
    return (next) => {
      return (action) => {
        if (typeof action === 'function') {
          return action(dispatch, extraArgument)
        }
        return next(action)
      }
    }
  }
}

const thunk = createThunkMiddleware()
thunk.withExtraArgument = createThunkMiddleware

export default  thunk
收藏