对redux的认识(源码深度解读)

用redux写过一些小项目,感觉有段时间不用快要忘记。所以写下我对redux的理解(和react-redux结合使用)。作为笔记。redux版本是3.6.0。react-redux版本是5.0.4

首先打开github的redux项目,在源码中,主要有以下文件:
这里写图片描述

这也是redux的组成部分。utils文件夹只有一个warning.js,用来打印错误信息。index.js用来导出相关内容。关键的是其他五个文件。

applyMiddleware.jsbindActionCreator.jscombineReducers.jscompose.jscreateStore.js

一个redux的流程是这样的。

这里写图片描述

1、在View上,用户会做出一个动作(比如点击)。

2、Store通过其方法dispatch这个动作,这个动作往往是一个对象({type: 'click', payload: {}}),或者是一个函数(需要redux-thunk或其他中间件处理)。对于Action,一般都是大写并且声明为常量。

store.dispatch({
  type: 'SELECT_BOOKNAME',
  payload: {
    text: 'react'
  }
})

3、Reducers会收到这个动作,并进行相应的处理。

import { SELECT_BOOKNAME } from '../actions';

const selectedBookName = (state = 'java', action) => {
  switch (action.type) {
    case SELECT_BOOKNAME:
      return action.payload.text;
    default:
      return state;
  }
}
export default selectedBookName;

reduce处理会改变应用的state(redux应用中,只有一个store)。如上action.payload.text。默认情况下state的值为java,现在变成了react(action中传过来的参数)。但是这个state不是应用整个的state,正如这个reducer只是一个子reducer而已。所以这里作为参数的state实际上是应用的state.selectedBookName属性。

4、state改变了,一般而言,页面(View)会重新render。依赖于state的数据也会改变。

下面是源码分析

compose

本来把compose放在后面,想想还是提前了。
compose是一个辅助函数,效果很简单,它只是简化了深层嵌套调用函数的问题。compose(f, g)的行为等价于(...args) => f(g(...args))。简单的说,compose(f, g)是一个函数,接受的参数会传给函数g,函数g执行,将函数返回的结果作为函数f的参数。然后执行函数fcompose(f, g)的返回值即函数f执行后的返回值。

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

这里主要使用的是Array.prototype.reduce函数reduce(function(acc, currentValue, currentIndex, arr), initialValue)。其中initialValue是可选的。如果提供了initialValue,那么acc的值是initialValuecurrentValue的值是数组的第一个元素。如果没提供initialValue,那么acc的值是数组的第一个值,currentValue的值是数组的第二个值。

Array.prototype.reduce函数中的参数函数(callback)将会从左到右处理数组的每一个值。然后将最终的结果返回。在这里,callback返回一个函数。

(...args) => a(b(...args));

所以compose函数的返回值也是一个函数(A),其接受一个或多个参数,然后将参数传入到compose函数中最后一个参数执行。执行的结果作为compose函数中倒数第二个函数的参数。依次执行。

compose(fn1, fn2, fn3)(1,2,3);
// 等价于
fn1(fn2(fn3(1,2,3)))

上述代码将参数1,2,3作为fn3的参数。fn3(1,2,3)执行的结果作为fn2的参数…

createStore

去掉了注释和辅助函数以及store的一些函数的具体的实现(dispatch、getState、subscribe、replaceReducer)。将精力全部集中到createStore这个函数。

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

    return enhancer(createStore)(reducer, preloadedState) // 注1
  }
  if (typeof reducer !== 'function') {
    throw new Error('Expected the reducer to be a function.')
  }

  let currentReducer = reducer
  let currentState = preloadedState
  let currentListeners = []
  let nextListeners = currentListeners
  let isDispatching = false
  function ensureCanMutateNextListeners() {}  
  function subscribe(listener) {}
  function dispatch(action) {}
  function replaceReducer(nextReducer) {}
  function observable() {}

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

在创建store的时候,我们可以传入初始状态(preloadedState),但这不是必须的。所以一开始会判断。

enhancer一般是applyMiddleware或者是compose函数。通过它可以使用一些第三方组件,比如redux-loggerredux-thunk等。这个参数也是可选的。

一般我们创建的store是这样的。

const store = createStore(
   reducers,
   preloadState,
   compose(
     applyMiddleware(...middleware),
     window.devToolsExtension ? window.devToolsExtension() : f => f
   )
 )
 // 或者是
 const store = createStore(
   reducers,
   preloadState,
   applyMiddleware(...middleware)
 )

不管如何,如果有enhancer,那么就会转而执行enhancer(注1)。假设这里是applyMiddleware。执行applyMiddleware并传入参数。并把函数本身传入作为参数。然后执行enhancer(createStore)(返回的肯定是一个函数,从下文applyMiddleware可以看到)并传入(reducer, preloadedState)

return enhancer(createStore)(reducer, preloadedState) // 注1

applyMiddleware

// applyMiddleware.js
export default function applyMiddleware(...middlewares) {
  return (createStore) => (reducer, preloadedState, enhancer) => { // 注2
    const store = createStore(reducer, preloadedState, enhancer) // 注3
    let dispatch = store.dispatch
    let chain = []

    const middlewareAPI = {
      getState: store.getState,
      dispatch: (action) => dispatch(action)
    }
    chain = middlewares.map(middleware => middleware(middlewareAPI))
    dispatch = compose(...chain)(store.dispatch)

    return {
      ...store,
      dispatch
    }
  }
}

注2这里不是很容易理解的,需要对照上文的createStore

return enhancer(createStore)(reducer, preloadedState) //1
return (createStore) => (reducer, preloadedState, enhancer) => { //2

相互对照,方便理解。那么流程就是在createStore的时候,如果有enhancer(如applyMiddleware),就先执行enhancer,并传入createStore函数本身以及它的参数reducer, preloadState

再查看注3,也就是applyMiddleware的开始部分。因为applyMiddleware的参数是一个个组件,对于这些组件,执行createStore函数,并传入第一次createStore传下来的两个参数。所以流程就是createStore->applyMiddleware->createStore。由于这次的enhancer是undefined,所以在createStore.js中会继续往下执行,返回一个store。绕了个弯。

const store = createStore(reducer, preloadedState, enhancer)

然后从注3往下执行。关键来了。先插述点东西。

————————————————————————-
one:
以中间件的最简单方式为例,创建一个中间件通常是这样的(如logger中间件):

const logger = store => next => action => { // next相当于dispatch
  console.log('dispatching', action);
  let result = next(action); // 相当于dispatch(action),返回的结果仍然是action
  console.log('next state', store.getState()); // 注4
  return result; // 返回action,供其他中间件处理
}

————————————————————————-
插述完毕。
之前说到从注3往下执行。接下来分析这一块代码:

// applyMiddleware.js
let dispatch = store.dispatch
let chain = []

const middlewareAPI = {
  getState: store.getState,
  dispatch: (action) => dispatch(action)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch)

middlewares是所有中间件组成的数组。调用map方法,传入中间件(是一个函数,比如上述的logger中间件),并执行这个函数,传入middlewareAPI变量(相当于store)。

const logger = store => next => action => { // next相当于dispatch,往下文看
  console.log('dispatching', action);
  let result = next(action); // 相当于dispatch(action),返回的结果仍然是action
  console.log('next state', store.getState()); // 注4
  return result; // 返回action,供其他中间件处理
}

再看看吧,logger是middlewaremiddlewareAPI相当于store,所以可以在注4调用store.getState()方法。

middlewares.map返回值是一个数组,赋值给chain,数组的每一项是函数。即中间件(logger)执行后返回的函数(fn = next => action => {})。

然后增强dispatch。

dispatch = compose(...chain)(store.dispatch)

刚说的chain中的每一项是个数组,并且compose的作用就是从右到左执行它的函数参数。所以需要执行fn = next => action => {}这个函数,这个函数的返回值还是一个函数(爽不爽!!!,函数是这样的:fn = action => {},这个函数能接受action作为参数,当然是dispatch了)。对于fn = next => action => {}传入store.dispatch,所以next就是store.dispatch(上文也提到)。然后将返回的函数next再传递给倒数第二个参数(从右往左),然后一直往左,直到第一个。想象一下:

chain = [returnedLogger1, returnedLogger2, returnedLogger3];

首先store.dispatch传递给returnedLogger3,返回值是一个函数,fn = action => {},能接受action作为参数,其实际上还是store.dispatch。所以就是store.dispatch一直作为参数从右向左传递,直到compose最左边的函数参数执行完毕,返回的store.dispatch赋值给最初的dispatch。666666版本的dispatch出现了!!!

然后就是applyMiddleware.js内层函数的返回值了。

return {
  ...store,
  dispatch
}

...store表示解构store这个对象(其中有dispatch,subscribe,getState等),其中dispatch会被增强版本的dispatch覆盖。

至此,applyMiddleware.js分析完毕。

下面是一个测试:

const logger1 = store => next => action => {
  console.log(111);
  let result = next(action);
  console.log(222);
  return result;
}

const logger2 = store => next => action => {
  console.log(111111);
  let result = next(action);
  console.log(222222);
  return result;
} 

const logger3 = store => next => action => {
  console.log(111111111);
  let result = next(action);
  console.log(222222222);
  return result;
} 

const configureStore = (preloadState={aaa: 'aaaa'}) => {
  const store = createStore(
    reducers,
    preloadState,
    compose(
      applyMiddleware(thunk, logger1, logger2, logger3),
      window.devToolsExtension ? window.devToolsExtension() : f => f
    )
  )
 return store;
}

applyMiddleware(thunk, logger1, logger2, logger3),这种形式,打印出的结果是:

111, 111111, 111111111, 222222222, 222222, 222

combineReducers

export default function combineReducers(reducers) {
  const reducerKeys = Object.keys(reducers)
  const finalReducers = {}
  for (let i = 0; i < reducerKeys.length; i++) {
    const key = reducerKeys[i]

    if (process.env.NODE_ENV !== 'production') {
      if (typeof reducers[key] === 'undefined') { // 诚心找事吧!
        warning(`No reducer provided for key "${key}"`)
      }
    }

    if (typeof reducers[key] === 'function') { // 小的reducer肯定是function啊
      finalReducers[key] = reducers[key]
    }
  }
  const finalReducerKeys = Object.keys(finalReducers)

  // 忽略中间这些异常处理吧,比如preloadState中的key在reducers(参数reducer,是一个对象)中不存在。
  return function combination(state = {}, action) { // 注5
    let hasChanged = false
    const nextState = {}
    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)
      if (typeof nextStateForKey === 'undefined') {
        const errorMessage = getUndefinedStateErrorMessage(key, action)
        throw new Error(errorMessage)
      }
      nextState[key] = nextStateForKey
      hasChanged = hasChanged || nextStateForKey !== previousStateForKey
    }
    return hasChanged ? nextState : state
  }
}

一般而言,finalReducersreducers是相同的(见上述代码)。从异常处理下面开始分析。finalReducerKeys是一个数组。

// 例子reducers
export default combineReducers({
  postByBookName,
  selectedBookName,
  user
})

比如上面这个reducers,finalReducerKeys的值是:

["postByBookName","selectedBookName","user"]

由于在createStore的时候传入了大的reducer,所以store.dispatch一个action的时候大的reducer会处理这个action
createStore.js中有两行代码:

let currentReducer = reducer
let currentState = preloadedState
// 下面这行代码在store.dispatch函数中
currentState = currentReducer(currentState, action)

反映在上述代码就是执行注5的函数。

return function combination(state = {}, action) { // 注5

关键是这5行代码,以上述例子reducers其中的selectedBookName为例。

const key = finalReducerKeys[i] 
const reducer = finalReducers[key] 
const previousStateForKey = state[key]
const nextStateForKey = reducer(previousStateForKey, action)

nextState[key] = nextStateForKey

key是字符串selectedBookName
reducerselectedBookName对应的reducer。我的是:

const selectedBookName = (state = 'java', action) => {
  switch (action.type) {
    case SELECT_BOOKNAME:
      return action.payload.text;
    default:
      return state;
  }
}
export default selectedBookName;

previousStateForKeyundefined
nextStateForKeyselectedBookName函数执行的结果,如果匹配上了返回action.payload.text,假如是”react”。
nextState["selectedBookName"] = "react"

此时由于previousStateForKey不等于nextStateForKey,所以hasChanged的值为true。返回nextState

至此,combineReducers.js分析完毕。

bindActionCreators

这是个辅助函数。比如:

const test = () => {
  type: 'test',
}

以前我们的用法是dispatch(test()),使用了这个辅助函数,我们只需要test()。就是少写了dispatch这么回事。一般情况下我不会用到。

function bindActionCreator(actionCreator, dispatch) {
  return (...args) => dispatch(actionCreator(...args))
}

export default function bindActionCreators(actionCreators, dispatch) {
  if (typeof actionCreators === 'function') {
    return bindActionCreator(actionCreators, dispatch)
  }

  const keys = Object.keys(actionCreators)
  const boundActionCreators = {}
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    const actionCreator = actionCreators[key]
    if (typeof actionCreator === 'function') {
      boundActionCreators[key] = bindActionCreator(actionCreator, dispatch)
    } else {
      warning(`bindActionCreators expected a function actionCreator for key '${key}', instead received type '${typeof actionCreator}'.`)
    }
  }
  return boundActionCreators
}

上述源码分为两种情况,一种是typeof actionCreator === 'function',就是我举例的那种。一种是actionCreator是一个对象。通常在这种情况出现:

import * as Actions from '../actions'

这样Actions是一个对象,每个import出的函数就是Actions的一个属性值。然后向一种一样的处理。通常我不会使用这个辅助函数,当我使用react-redux的时候,我可能会用到。

import * as CounterActions from '../actions'

const mapStateToProps = (state) => ({
  counter: state.counter
})

function mapDispatchToProps(dispatch) {
  return bindActionCreators(CounterActions, dispatch)
}

export default connect(mapStateToProps, mapDispatchToProps)(Counter)

然后我直接调用CounterActions.myAction()就直接dispatch了。

结语

花了一晚上时间,梳理了redux的结构,加深了理解。对源码进行了细致的分析。如果有更多的想要和我交流,可以加我:

QQ: 1227620310
微信: a127620310
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Redux 是一种 JavaScript 状态管理库,它可以帮助我们管理应用程序中的状态,并且可以在整个应用程序中共享状态。Redux 的实现原理主要有以下几个方面: 1. Store:Redux 应用程序中的所有状态都存储在一个单一的、不可变的存储区域中,称为 "store"。store 是一个纯 JavaScript 对象,它包含了整个应用程序的状态。store 还包含了一些方法,用于获取状态、监听状态变化、更新状态等操作。 2. Action:Redux 应用程序中的状态只能通过 "action" 来修改。action 是一个描述状态变化的普通 JavaScript 对象,它必须包含一个 "type" 属性,用于描述变化的类型。action 还可以包含一些其他的数据,用于描述变化的具体内容。 3. Reducer:Redux 应用程序中的状态修改是通过 "reducer" 来实现的。reducer 是一个纯函数,它接受当前的状态和一个 action,然后返回一个新的状态。reducer 不能修改原来的状态,它只能返回一个全新的状态。Redux 应用程序中可能会有多个 reducer,每个 reducer 负责管理一个部分状态。 4. Dispatch:Redux 应用程序中的状态修改是通过 "dispatch" 来触发的。dispatch 是一个方法,它接受一个 action,然后将 action 发送给所有的 reducer。每个 reducer 都会接收到 action,然后根据 action 的类型来修改自己管理的状态。 5. Subscribe:Redux 应用程序中的状态变化是通过 "subscribe" 来监听的。subscribe 是一个方法,它接受一个回调函数,每当状态发生变化时就会调用这个回调函数。我们可以在回调函数中获取最新的状态,然后根据状态来更新应用程序的界面。 综上所述,Redux 的实现原理就是通过 store、action、reducer、dispatch 和 subscribe 这些机制来实现状态管理的。通过这些机制,我们可以将应用程序中的状态集中管理,从而带来更好的可维护性、可扩展性和可重用性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值