博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
redux v4.0.0 源码分析
阅读量:6607 次
发布时间:2019-06-24

本文共 7910 字,大约阅读时间需要 26 分钟。

文章来自我个人的

这周决定宅着不出去做完平日里没做完的事情。其中之一就是分析redux的源码。我看的版本是v4.0.0。Redux的作者是。俄罗斯人。非常好的人,还回复过我的。

Redux本身代码量是很少的。典型的文档比代码多系列。测试也写得很全。Redux的标语是

Predictable state container for JavaScript apps

Predictable可以预测的状态管理容器。可预测最主要的就是可以做time machine,时间旅行。可以回滚到上次的状态。Redux对初学者来说可能是非常complex的。并且有些过于啰嗦。Redux本身不属于React的一部分。React-Redux和Redux是两个不同的项目。Redux本身只是一个很小的状态管理库。可以应用到其他的框架上。

看源码之前要做的事情就是摸熟这个框架或者codebase的用法。这样就不会觉得陌生。

首先要做的事情是从Gihub把Redux的源码克隆下来。

git clone git@github.com:reactjs/redux.git

克隆下来后为了调试代码。我自己写了个webpack.config.js,开个dev server配合vscode 的debugger in chrome进行调试。其实我也不知道正规的看代码方法吧(其实是找了很久没找到,又不想调试test case)。反正现在也不纠结,我就先这样。贴一下配置。

先进入redux根目录创建webpack.config.js。再开个debugger目录。

touch webpack.config.jsmkdir debugger复制代码

装依赖

yarn add -D webpackyarn add -D webpack-cliyarn add -D html-webpack-pluginyarn add -D webpack-dev-serveryarn复制代码

额 不做这一步也可以,只是我喜欢这样。然后弄完可以直接用vscode调试了。

webpack.config.js

const HtmlWebpackPlugin = require('html-webpack-plugin')module.exports = {  mode: 'development',  entry: './debugger/index.js',  devtool: 'inline-source-map',  output: {    path: __dirname + '/dist',    filename: 'index_debugger_bundle.js'  },  plugins: [    new HtmlWebpackPlugin({      template: './debugger/index.html'    })  ]}复制代码

debugger/index.html

  
Document 哭哭惹复制代码

debugger/index.js

import * as redux from '../src'console.log(redux)复制代码

package.json

"scripts": {  "debugger": "webpack-dev-server --open --port=2333"}复制代码

改一下vscode调试的配置就可以了。详细的自己看一下吧。source-map必须要开不然这插件就是shit。

项目结构

以下是每个文件的用处。懒得打字所以一大部分都是抄袭的。贴上。因为原作者的版本比较旧了。所以自己补充了几个文红没写到的。

applyMiddlewar.js 使用自定义的 middleware 来扩展 Redux

bindActionCreators.js 把 action creators 转成拥有同名 keys 的对象,使用时可以直接调用

combineReducers.js 一个比较大的应用,需要对 reducer 函数 进行拆分,拆分后的每一块独立负责管理 state 的一部分

compose.js 从右到左来组合多个函数,函数编程中常用到

createStore.js 创建一个 Redux Store 来放所有的state

utils/warnimng.js 控制台输出一个警告,我们可以不用看

utils/actionTypes.js redux内部使用的3个action

utils/isPlanObject.js 判断对象是不是纯对象

createStore

所有的一切都基于createStore创建出来的store。可以说是一切的开始。createStore函数接受三个参数。

function createStore(reducer, preloadedState, enhancer)复制代码

createStore暴露出的方法有以下这些

方法名 作用
dispatch dispatch一个action触发状态变化,调用reducer创建新的store
subscribe 添加listener状态改变的时候会被触发listener的回调函数
getState 读取状态树管理的状态
replaceReducer 替换当前使用的Reducer。如果你想要动态替换Reducer的话可以用到。
$$observable 私有属性 不知道干嘛的 叫我看

createStore函数就是一个Observser实现的话也没啥好看的。读一读代码就能理解了。这里就不详细写了。不是很懂的地方有一个就是ensureCanMutateNextListeners的作用。暂时我还没有理解。

MiddleWare 实现

redux实现middleware和compose函数有很多的关系。这个compose函数非常的简单。核心是这样。我删掉了一些不(特别)重要的代码。

const compose = (...funcs) => {  return funcs.reduce((a, b) => (...args) => a(b(...args)))}复制代码

这个compose只做一件事情。把函数组合起来。就是函数式编程的那个组合。返回一个从右到左执行的compose function。比如传入(a,b,c)这样的函数参数这样的执行顺序: c(b(a(...args)))。我们可以做个实验,你可以直接把代码复制到控制台里面执行。

const a = () => console.log('a')const b = () => console.log('b')const c = () => console.log('c')compose(a,b,c)()复制代码

输出的结果会是: c,b,a。但是如果我们把这些函数做一个更加高阶的处理。让函数第一次执行的时候返回函数。变成这样的话。会发生一个很有趣的现象。

const a = ( next ) => ( param ) => { console.log(param); next('b'); }const b = ( next ) => ( param ) => { console.log(param); next('c'); }const c = ( next ) => ( param ) => { console.log(param); next('d'); }const d = (param) => console.log(param)const cp = compose(a,b,c)(d)// executecp('a')复制代码

输出的结果会是a,b,c,d。和之前的函数版本相比。此时的compose function函数拥有了控制是否执行下一个函数的能力。并且通过调用next来执行下一个。同时它变成正序的。Redux利用了这一特性。

const compose = (...funcs) => {  return funcs.reduce((a, b) => (...args) => a(b(...args)))}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)    return {      ...store,      dispatch    }  }}复制代码

Redux把store => next => action的最外层通过执行middlewares.map(middleware => middleware(middlewareAPI))传入参数把函数变成了next => action的形式完成了中间件的模式。

combineReducer 实现

combineReducer的作用是把零零散散的reducer拼起来或者说把复杂的单个reducer拆分成小的reducer。实现的原理都在代码上。以下代码做了一些精简的处理,去掉了一些不是特别重要的警告。

export default function combineReducers(reducers) {  // 获取reducers的key  const reducerKeys = Object.keys(reducers)  const finalReducers = {}  // 处理reducerKeys  for (let i = 0; i < reducerKeys.length; i++) {    const key = reducerKeys[i]    // 非开发环境下 如果reducers[key]为undefined 抛出警告    if (process.env.NODE_ENV !== 'production') {      if (typeof reducers[key] === 'undefined') {        warning(`No reducer provided for key "${key}"`)      }    }    // 判断是否为方法    if (typeof reducers[key] === 'function') {      finalReducers[key] = reducers[key]    }  }  // 获取finalReducerKeys的所有key  const finalReducerKeys = Object.keys(finalReducers)  // 组合state 原理是每次执行调用每个传过来的reducers  // 最终通过nextState拼出一个最大的state  // 通过`hasChanged`做了缓存处理  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)      // 如果nextStateForKey执行后的结果为undefined 说明该reducer返回的      // 结果是undefined 会抛出异常      if (typeof nextStateForKey === 'undefined') {        const errorMessage = getUndefinedStateErrorMessage(key, action)        throw new Error(errorMessage)      }      nextState[key] = nextStateForKey      // 和上次的状态比较 如果一致就为false。返回上一次的状态。      hasChanged = hasChanged || nextStateForKey !== previousStateForKey    }    return hasChanged ? nextState : state  }}复制代码

bindActionCreators 实现

bindActionCreators的最常见用的地方可能是在react-redux调用mapDispatchToProps的时候。

const TodoAction = (...args) => { type: 'TODO', { ...args } }function mapDispatchToProps(dispatch) {  actions: bindActionCreators(TodoAction, dispatch)}复制代码

不用bindActionCreators的话会是这样写。就如下面的bindActionCreator函数基本一致。要注意源码是没有s那个。

调用有s那个传一个对象。写起来会是这样。原理是遍历执行bindActionCreators

import * as TodoActions from '../actions/todo'function mapDispatchToProps(dispatch) {  actions: bindActionCreators(TodoActions, dispatch)}复制代码

源码如下:

function mapDispatchToProps(dispatch) {  actions:  (...args) => {     dispatch(TodoActions(args))  }}复制代码

源码如下:

function bindActionCreator(actionCreator, dispatch) {  return function() {    return dispatch(actionCreator.apply(this, arguments))  }}export default function bindActionCreators(actionCreators, dispatch) {  if (typeof actionCreators === 'function') {    return bindActionCreator(actionCreators, dispatch)  }  if (typeof actionCreators !== 'object' || actionCreators === null) {    throw new Error(      `bindActionCreators expected an object or a function, instead received ${        actionCreators === null ? 'null' : typeof actionCreators      }. ` +        `Did you write "import ActionCreators from" instead of "import * as ActionCreators from"?`    )  }  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)    }  }  return boundActionCreators}复制代码

isDispatching的作用

这个变量用来处理reducer 中 调用 dispatch,导致死循环 的情况。

如下代码,在reducer里dispatch 又被调用,这种情况redux是不允许的。

var reducer = function(state, action){    switch (action.type) {        case 'add_todo':            store.dispatch({
type: 'yyy'}); // 调用B return state; default: return state; }};var store = redux.createStore(reducer, []);store.dispacth({
type: 'xxx'}); // 调用A复制代码

转载于:https://juejin.im/post/5ca18ebe51882543d14575c6

你可能感兴趣的文章
Github排名中的Python web框架Flask学习方法,非常受用!
查看>>
PDF如何取消高亮
查看>>
iOS 打包.a文件
查看>>
UI设计师保持设计高水平的方法是什么?
查看>>
web网站服务
查看>>
Android开发之InstanceState详解
查看>>
golang入门
查看>>
Cisco设备及网络环境搭建模拟利器
查看>>
Python编程系统资源
查看>>
Ubuntu环境下自动定时启动任务
查看>>
UItableview
查看>>
ubuntu下virtualbox安装增强包
查看>>
【Demo】 生成二维码 和 条形码
查看>>
在update语句中使用子查询
查看>>
win8.1 开机慢 快速解决
查看>>
对vi编辑器的总结
查看>>
ssl信息和如何在openssl自建一个CA服务器
查看>>
lamp环境搭建mysql5.1+httd2.2+php5.3
查看>>
form 表单,点击按钮,自动刷新提交的bug
查看>>
Elephantbird介绍
查看>>