【react进阶(二)】:教你正确理解、合理使用Redux及Redux的源码分析

教你正确理解、合理使用Redux及Redux的源码分析

经过本文你会学到什么

  • 咱们为何要使用Redux
  • Redux应如何使用
  • 从源码分析Redux
  • Redux的弊端
  • Redux优秀实践

1、咱们为何要使用Redux

核心:合理高效的管理咱们应用的状态;javascript

若是咱们没有Redux会如何进行状态共享
  1. 在React中咱们会使用父子组件经过props进行状态传递;
  2. 在React中经过context API进行状态共享;
  3. 或者你直接经过localstorage等本地存储手段进行数据共享;

这些管理方式会带来什么样的麻烦呢?html

  1. 组件嵌套层次过深咱们来回传递贼鸡儿麻烦;
  2. 可能须要共享状态的组件没有组件层级关系;
  3. context API拿来传递些全局状态好比说主题、字体等还行,复杂的状态也不是能很好的管理;(旧版本的React context API当状态变动还会带来没法rerender的问题,这里就不作展开;)
  4. 状态没法预测,当出现bug的时候比较难定位问题;

2、Redux如何使用

  1. createStore方法建立一个store;(一个store中是一个对象树)
  2. store.subscribe建立变化监听器;(每当 dispatch action 的时候就会执行)
  3. store.dispatch(action)触发action,action是描述发生什么的一个对象;
  4. reducer接收上一次的state(或是初始state)和action,并返回新的 state 的函数;
Redux的三大核心原则
  1. 单一数据源:整个应用的 state 被储存在一棵 object tree 中,而且这个 object tree 只存在于惟一一个 store 中。
  2. State是只读的:惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
  3. 使用纯函数修改:为了描述 action 如何改变 state tree ,你须要编写 reducers
代码示例
// reducer
const counter = (state = 0, action) => {  switch (action.type) {  case 'INCREMENT':  return state + 1  case 'DECREMENT':  return state - 1  default:  return state  } }  // 建立store const store = createStore(counter)  const rootEl = document.getElementById('root') const render = () => ReactDOM.render(  <Counter  value={store.getState()}  onIncrement={() => store.dispatch({ type: 'INCREMENT' })}  onDecrement={() => store.dispatch({ type: 'DECREMENT' })}  />,  rootEl ) render()  // 注册监听器 store.subscribe(render) 复制代码

3、从源码分析Redux

Redux的源码实际上比较少,核心可能就两百行,比较适合初读源码的小伙伴们,下面咱们一块儿经过源码来探究如下Redux的原理。前端

1. createStore

createStore这个方法接受三个参数:java

  1. reducer:接受一个函数(这个函数接受两个参数:state,action,返回新的state树)
  2. preloadedState:初始时的 state(先后端同构的时候也能够起做用)
  3. enhancer:扩展store的功能

返回了一个store对象,这个对象包含了四个值mysql

  1. dispatch: 接受一个plainobject的action,经过传入的Reducer计算出本次action以后的新state树
  2. subscribe: 注册监听:传入listener 存入nextListeners; 返回unsubscribe方法
  3. getState: 获取当前的state状态树
  4. replaceReducer:替换 store 当前用来计算 state 的 reducer。currentReducer =》 newReducer
export default function createStore<  S,  A extends Action,  Ext = {},  StateExt = never >(  // 接受一个函数(这个函数接受两个参数:state,action,返回新的state树)   reducer: Reducer<S, A>,  // 初始时的 state。 在同构应用中,你能够决定是否把服务端传来的 state 水合(hydrate)后传给它,  // 或者从以前保存的用户会话中恢复一个传给它。若是你使用 combineReducers 建立 reducer,它必须是一个普通对象,与传入的 keys 保持一样的结构   preloadedState?: PreloadedState<S> | StoreEnhancer<Ext, StateExt>,  // 扩展store的功能   enhancer?: StoreEnhancer<Ext, StateExt> ): Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext {  // 若是存在enhancer且是一个函数(高阶函数)  if (typeof enhancer !== 'undefined') {  if (typeof enhancer !== 'function') {  throw new Error('Expected the enhancer to be a function.')  }   return enhancer(createStore)(  reducer,  preloadedState as PreloadedState<S>  ) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext  }  // 存储的是咱们reducer函数  let currentReducer = reducer  // 目前的state  let currentState = preloadedState as S  // 存储监听器  let currentListeners: (() => void)[] | null = []  // 这里存储一份Listener是防止有人会在dispatch的过程当中进行subscribe/unsubscribe的骚操做  let nextListeners = currentListeners  // 是否处于dispatch中  let isDispatching = false   function ensureCanMutateNextListeners() {  if (nextListeners === currentListeners) {  // slice: 该方法并不会修改数组,而是返回一个子数组。  nextListeners = currentListeners.slice()  }  }  // 获取当前的state  function getState(): S {  if (isDispatching) {  throw new Error('简化')  }   return currentState as S  }   // 注册监听:传入listener 存入nextListeners; 返回unsubscribe方法  function subscribe(listener: () => void) {  if (isDispatching) {  throw new Error('简化')  }   let isSubscribed = true   ensureCanMutateNextListeners()  nextListeners.push(listener)   return function unsubscribe() {  if (!isSubscribed) {  return  }   if (isDispatching) {  throw new Error('简化')  }   isSubscribed = false   ensureCanMutateNextListeners()  const index = nextListeners.indexOf(listener)  nextListeners.splice(index, 1)  currentListeners = null  }  }   // 接受一个plainobject的action  function dispatch(action: A) {  if (isDispatching) {  throw new Error('Reducers may not dispatch actions.')  }   try {  isDispatching = true  // 经过传入的Reducer计算出本次action以后的新state树  currentState = currentReducer(currentState, action)  } finally {  isDispatching = false  }  // 此时:nextListeners = currentListeners = listeners  const listeners = (currentListeners = nextListeners)  // 遍历执行监听器  for (let i = 0; i < listeners.length; i++) {  const listener = listeners[i]  listener()  }  // 返回当前传入的action  return action  }   // 替换 store 当前用来计算 state 的 reducer。currentReducer =》 newReducer  function replaceReducer<NewState, NewActions extends A>(  nextReducer: Reducer<NewState, NewActions>  ): Store<ExtendState<NewState, StateExt>, NewActions, StateExt, Ext> & Ext {  if (typeof nextReducer !== 'function') {  throw new Error('Expected the nextReducer to be a function.')  }   // TODO: do this more elegantly  ;((currentReducer as unknown) as Reducer<  NewState,  NewActions  >) = nextReducer   dispatch({ type: ActionTypes.REPLACE } as A)  // change the type of the store by casting it to the new store  return (store as unknown) as Store<  ExtendState<NewState, StateExt>,  NewActions,  StateExt,  Ext  > &  Ext  }   // store建立自动dispatch的初始化action,建立咱们的初始化state树  dispatch({ type: ActionTypes.INIT } as A)   const store = ({  dispatch: dispatch as Dispatch<A>,  subscribe,  getState,  replaceReducer  } as unknown) as Store<ExtendState<S, StateExt>, A, StateExt, Ext> & Ext  return store } 复制代码

2. combineReducers

此方法就接收一个参数,这参数是一个reducerMap对象,相似{routing: routingReducer};react

export default function combineReducers(reducers: ReducersMapObject) {
 // store tree上的key  const reducerKeys = Object.keys(reducers)  const finalReducers: ReducersMapObject = {}  // 遍历拷贝传入的reducersMap到finalReducers  for (let i = 0; i < reducerKeys.length; i++) {  const key = reducerKeys[i]   if (typeof reducers[key] === 'function') {  finalReducers[key] = reducers[key]  }  }  // finalReducerKeys也就是reducersMap的key的集合数组  const finalReducerKeys = Object.keys(finalReducers)   let shapeAssertionError: Error  try {  // 这里去判断了一下reducers的对应的reducer的合理性  assertReducerShape(finalReducers)  } catch (e) {  shapeAssertionError = e  }   // 返回了一个函数:也就是咱们传入到createStore方法中的第一个参数,传入state,action,返回新的state树  return function combination(  state: StateFromReducersMapObject<typeof reducers> = {},  action: AnyAction  ) {  if (shapeAssertionError) {  throw shapeAssertionError  }  // 是否须要返回新state的标识  let hasChanged = false  const nextState: StateFromReducersMapObject<typeof reducers> = {}  // 遍历去执行每一个reducer,根据对应的key,生成新的nextState  for (let i = 0; i < finalReducerKeys.length; i++) {  const key = finalReducerKeys[i]  const reducer = finalReducers[key]  const previousStateForKey = state[key] // 这里previousStateForKey存储的是对于变化前的store树上key的state  const nextStateForKey = reducer(previousStateForKey, action)  if (typeof nextStateForKey === 'undefined') {  const errorMessage = getUndefinedStateErrorMessage(key, action)  throw new Error(errorMessage)  }  // nextState:新store tree  nextState[key] = nextStateForKey  // 若是有一个store tree上对应一个key的value改变了,那hasChanged就置为true  hasChanged = hasChanged || nextStateForKey !== previousStateForKey  }  // 若是reducersMap keys不等于 state的keys 的length 也一样 hasChanged 为 true  // 也就是说默认的state没有传,第一次的时候就会return nextState;nextState的value也就是reducers的默认值  hasChanged =  hasChanged || finalReducerKeys.length !== Object.keys(state).length  return hasChanged ? nextState : state  } } 复制代码

3. applyMiddleware

这个方法很重要,咱们经过传入的中间件来达到加强咱们store.dispatch方法的目的;git

applyMiddleware方法接受一个数组做为参数: middlewares-传入数组<中间件>;github

applyMiddleware方法是一个很是典型的函数式编程的写法,为何这么说呢?

redux做者为啥不直接传入多个参数,还非要将这个函数分解为多个单参数函数进行调用呢?这就是函数式编程的理念,你品,你细品;web

export default function applyMiddleware(  ...middlewares: Middleware[] ): StoreEnhancer<any> {  // 典型的函数式编程写法  return (createStore: StoreCreator) => <S, A extends AnyAction>(  reducer: Reducer<S, A>,  ...args: any[]  ) => {  // 生成store  const store = createStore(reducer, ...args)  let dispatch: Dispatch = () => {  throw new Error(  'Dispatching while constructing your middleware is not allowed. ' +  'Other middleware would not be applied to this dispatch.'  )  }   const middlewareAPI: MiddlewareAPI = {  getState: store.getState,  dispatch: (action, ...args) => dispatch(action, ...args)  }  // 遍历去执行middleware的方法(传入参数getState、dispatch)  // middleware返回一个接受next参数的函数  // 因此chain其实是存储了不少函数的数组  const chain = middlewares.map(middleware => middleware(middlewareAPI))  // 对正常建立的store.dispatch进行加强  dispatch = compose<typeof dispatch>(...chain)(store.dispatch)   return {  ...store,  dispatch  }  } } 复制代码
本身实现一个简单的Redux Middleware
// 这是咱们的一个用来打印action和 dispatch后的state的middleware
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  } } 复制代码

4. compose

compose函数精髓所在:funs = [a,b,c,d,e] => return (...args) => a(b(c(d(e(...args)))))sql

这个方法在koa2的源码中也有体现,具体可看:Koa源码分析(中间件执行机制、Koa2与Koa1比较)

export default function compose(...funcs: Function[]) {
 if (funcs.length === 0) {  // infer the argument type so it is usable in inference down the line  return <T>(arg: T) => arg  }   if (funcs.length === 1) {  return funcs[0]  }  // compose函数精髓所在:funs = [a,b,c,d,e] => return (...args) => a(b(c(d(e(...args)))))  return funcs.reduce((a, b) => (...args: any) => a(b(...args))) } 复制代码

5. BindActionCreators

  • actionCreateors: : 一个 action creator,或者一个 value 是 action creator 的对象。
  • dispatch: 一个由 Store 实例提供的 dispatch 函数。
export default function bindActionCreators(  actionCreators: ActionCreator<any> | ActionCreatorsMapObject,  dispatch: Dispatch ) {  // 若是传入的actionCreateors就是一个函数直接返回一个dispatch这个action的包装函数  if (typeof actionCreators === 'function') {  return bindActionCreator(actionCreators, dispatch)  }  // Map: key:ActionCreator的函数名,value: 一个dispatch这个action的包装函数  const boundActionCreators: ActionCreatorsMapObject = {}  for (const key in actionCreators) {  const actionCreator = actionCreators[key]  if (typeof actionCreator === 'function') {  boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)  }  }  return boundActionCreators }  // 传入dispatch 和 actionCreator 返回dispatch(action)的包装函数 function bindActionCreator<A extends AnyAction = AnyAction>(  actionCreator: ActionCreator<A>,  dispatch: Dispatch ) {  return function (this: any, ...args: any[]) {  return dispatch(actionCreator.apply(this, args))  } } 复制代码
// 代码片断:具体代码可查看https://www.redux.org.cn/docs/api/bindActionCreators.html
this.boundActionCreators = bindActionCreators(TodoActionCreators, dispatch); console.log(this.boundActionCreators); // { // addTodo: Function, // removeTodo: Function // }  let action = TodoActionCreators.addTodo('Use Redux');  dispatch(action); 复制代码

6. Redux-thunk源码分析

redux默认是同步action,可是做者也提供了一个能够进行异步操做的中间件;

// redux-thunk源码很简单
// 让咱们action能够成为一个函数,这个函数能够访问到dispatch, getState这两个方法, // 最后返回一个plainobject action就行了 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; 复制代码
thunk函数究竟是啥

是函数式编程里面的特有名词,主要用于calculation delay,也就是延迟计算。

function wrapper(arg) {
 // 内部返回的函数就叫`thunk`  return function thunk() {  console.log('thunk running, arg: ', arg)  } } // 咱们经过调用wrapper来得到thunk const thunk = wrapper('wrapper arg')  // 而后在须要的地方再去执行thunk thunk() 复制代码
说一说另外一个解决异步操做问题的中间件-redux-saga

这个中间件应用的仍是很普遍的,好比说阿里的dva都是集成咱们的redux-saga; 核心是经过监听咱们的action操做,执行对应的side effects的task,结合咱们ES6的generator语法,配合saga提供的工具函数,能够很是方便的处理咱们的异步问题;

4、Redux带来的弊端

Redux虽然带来了比较高级的函数式编程思想以及Immutable数据不可变的特性,可是咱们普通开发者去很好的践行这些缺很累,为何累? 咱们通常使用react-redux的时候,都会定义不少的样板代码,特色的繁琐,好比说你的组织目录,须要有actions、reducers等等,简单的一个数据传递扭转,你须要修改多处的代码,并且这些代码均可能是差差很少的。

你可能还会带来心智负担,什么心智负担呢?

什么样的数据须要放在咱们的store中呢,是否我全部的状态都须要集中管理呢?答案固然是否认的,咱们须要将咱们须要全局共享的状态放入store中,组件内的状态不须要共享的固然仍是组件内的状态使用的好。(这里牵涉到一个性能问题,咱们通常在react项目中都会集成react-redux这个库,若是你几乎每一个组件都去connect订阅store中的数据,你会得到不少不须要的rerender,性能会大大下降。)

社区中有哪些解决方案

目前社区中为了解决咱们Redux的这些弊端,诞生了不少优秀的类库,固然,它们的核心仍是redux;

  1. dva
  2. rematch

6、Redux优秀实践

除了上面说的dvarematch,再推荐几个个类库辅助咱们进行项目开发:

  1. reselect

帮助你在使用react-redux作的过程当中,mapStateToProps方法不须要每次都重复计算state,会帮助你缓存计算的结果(固然,这是在你确实不须要从新计算的状况下,才会使用你缓存的结果);

  1. immutable-js or immer

均可以让你操做javascript的复杂数据类型时,没有心智负担,这里的心智负是指引用类型的反作用,你以前可能会经过深拷贝避免副做; 在redux的reducer中使用Object.assign或者ES6的... 运算符保持咱们的reducer函数是纯的,这两个类库均可以方便的让你安全的操做state; 这两个库都是很是优秀的,可是immer是后起之秀,两者实现的原理也有区别,就不展开讨论了。

咱们要学会合理的运用社区中优秀的方案解决咱们的项目开发中的问题。没有最好的,只有适合的

欢迎共同窗习

前端修炼指南

Node.js Koa2全栈实战

Flutter TodoList App开发

微前端实战与思考

typescript+mobx+react,拿走即用脚手架

参考以及优秀相关文章推荐

Redux 中文文档

redux 有什么缺点?

理解redux-thunk

2018 年咱们还有什么功能是 Redux 才适合作的吗?

本文使用 mdnice 排版

相关文章
相关标签/搜索