redux介绍

redux给我们暴露了这几个方法

{
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose
}

我们来依次介绍下

createStore

创建一个store的写法:

let store = createStore(reducer, preloadedState, enhancer);

createStore中的三个参数reducer, preloadedState, enhancer,后面两个是可选参数,
当我们只传两个参数,并且第二个参数是函数时,例如:

let store = createStore(reducer, enhancer);

通过阅读源码这段

if (typeof preloadedState === 'function' && typeof enhancer === 'undefined') {
enhancer = preloadedState
preloadedState = undefined
}

我们知道,上面的createStore会被改写成这样:

createStore(educer, undefined, enhancer)

再通过阅读源码这段:

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

会发现原来的调用方式再次发生了改变,变成了以下这种方式

enhancer(createStore)(reducer, undefined)

所以实例化一个createStore方法,以下两种是等价的:

第一种
const store = createStore(reducers, applyMiddleware(routeMiddleware, sagaMiddleware, postRedirectMiddleware, pageSizeMiddleware)); 第二种
const store = applyMiddleware(routeMiddleware, sagaMiddleware, postRedirectMiddleware, pageSizeMiddleware)(createStore)(reducers);

最后,返回的store对象提供了以下几个方法给我们使用

dispatch,
subscribe,
getState,
replaceReducer,

getState用来获取currentState,也就是总state值

subscribe注册多个监听函数,这些函数在开发者调用dispatch时会依次执行

dispatch的入参是一个对象action,直接会执行reducer(action)方法,并且批量执行subscribe监听的内容,dispatch执行结束后会返回一个action

replaceReducer替换当前的reducer,这个方法在异步的单应用中可以使用利用起来,例如,我们不想一次性将所有的reducer交给createStore初始化,而是当异步获取某个页面时,再将这个页面的reducer加上之前的旧reducer,通过replaceReducer方法来替换成最新的。这样做的好处是,devtools里的redux工具不会展示那么多的信息,只会展示访问过页面的信息,更有利于我们的开发,当然了由于reducer的减少,store的体积也会变小,页面执行速度更快。

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') {
finalReducers[key] = reducers[key]
}
}
const finalReducerKeys = Object.keys(finalReducers) let unexpectedKeyCache
if (process.env.NODE_ENV !== 'production') {
unexpectedKeyCache = {}
} let shapeAssertionError
try {
assertReducerShape(finalReducers)
} catch (e) {
shapeAssertionError = e
} return function combination(state = {}, action) {
if (shapeAssertionError) {
throw shapeAssertionError
} if (process.env.NODE_ENV !== 'production') {
const warningMessage = getUnexpectedStateShapeWarningMessage(
state,
finalReducers,
action,
unexpectedKeyCache
)
if (warningMessage) {
warning(warningMessage)
}
} 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
}
}

通常我们会这样使用

combineReducers({
a:reducer1,
b:reducer2
})

reducer1是一个函数,而combineReducers返回的任然是一个函数,只不过将每个reducer都遍历了一遍,最后返回的数据结构为

{
a:state1,
b:state2
}

而如果我们把a跟b替换成了一个唯一的路径path,这个path跟项目中每个页面的文件夹对应起来,例如:

combineReducers({
'component/page1/info/':reducer1,
'component/page2/user/':reducer2
})

而在取state数据的时候这样来取
state['component/page1/info/']是不是就可以通过文件夹路径实现模块化了,当然了我们离模块化还差一小步,就是不用人工来写路径path,而是交给构建工具(webpack的loader)来遍历完成。

compose

源码

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

个人觉得compose(a,b,c)(d)后的代码就是a(b(c(d))),compose后返回的任然是一个函数,可以接受参数,compose在后面介绍的代码中有所涉及。

applyMiddleware

源码

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.`
)
}
let chain = [] const middlewareAPI = {
getState: store.getState,
dispatch: (...args) => dispatch(...args)
}
chain = middlewares.map(middleware => middleware(middlewareAPI))
dispatch = compose(...chain)(store.dispatch) return {
...store,
dispatch
}
}
}

创建一个store的方法:


let store = applyMiddleware(middleware1,middleware2,middleware3)(createStore)(reducers)

中间件middleware1的代码

function middleware1({ getState }) {
return (next) => (action) => {
console.log('will dispatch', action)
let returnValue = next(action)
console.log('state after dispatch', getState())
return returnValue
}
}

applyMiddleware传入的参数为多个中间件,中间件的作用是在执行reducers中的switch之前,先执行中间件中的代码。

对应源码中的参数来讲的话,
实例参数middleware1、middleware2就相当于源码入参...middlewares,
实例参数createStore对应着源码入参createStore,
实例参数reducers对应着源码入参...args。

源码中的

const store = createStore(...args)

获取store对象供后面使用,middlewareAPI里的getState对应着store对象里的getState,getState()方法可以获取currentState,也就是redux对象树的总数据。

chain = middlewares.map(middleware => middleware(middlewareAPI))

middlewareAPI提供了getState这个方法,供中间件获取currenState,这时候chain获取的数组是一个返回值为函数的函数。

下面这行代码比较精彩

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

首先将最原始的store.dispatch方法作为入参,

dispatch(action)

就相当于
compose(...chain)(store.dispatch)(action), 同时也相当于
middleware1(middleware2(middleware3((store.dispatch))))(action),

当然这里的middleware1是已经只剩下闭包内的两层函数不是原来的三层函数体。

最先执行的是middleware1,
返回了next(action), 也就相当于
middleware2(middleware3((store.dispatch)))(action), 执行完后返回next(action)
就相当于middleware3((store.dispatch))(action), 执行完后返回next(action)
就相当于store.dispatch(action)

整个过程我们已经弄清楚了,applyMiddleware中间件的执行过程就是不断的next(action),
而只有最后的next才是执行dispatch,之前的next只代表的传递其他中间件,dispatch方法只在最后一个中间件里执行了一次。

个人觉得这个地方结合了compose之后写的比较精彩,而且设计的非常巧妙。

读redux源码总结的更多相关文章

  1. redux源码解读

    react在做大型项目的时候,前端的数据一般会越来越复杂,状态的变化难以跟踪.无法预测,而redux可以很好的结合react使用,保证数据的单向流动,可以很好的管理整个项目的状态,但是具体来说,下面是 ...

  2. Redux 源码解读 —— 从源码开始学 Redux

    已经快一年没有碰过 React 全家桶了,最近换了个项目组要用到 React 技术栈,所以最近又复习了一下:捡起旧知识的同时又有了一些新的收获,在这里作文以记之. 在阅读文章之前,最好已经知道如何使用 ...

  3. 从Redux源码探索最佳实践

    前言 Redux 已经历了几个年头,很多 React 技术栈开发者选用它,我也是其中一员.期间看过数次源码,从最开始为了弄清楚某一部分运行方式来解决一些 Bug,到后来看源码解答我的一些假设性疑问,到 ...

  4. 带你逐行阅读redux源码

    带你逐行阅读redux源码 redux版本:2019-7-17最新版:v4.0.4 git 地址:https://github.com/reduxjs/redux/tree/v4.0.4 redux目 ...

  5. 【读fastclick源码有感】彻底解决tap“点透”,提升移动端点击响应速度

    申明!!!最后发现判断有误,各位读读就好,正在研究中.....尼玛水太深了 前言 近期使用tap事件为老夫带来了这样那样的问题,其中一个问题是解决了点透还需要将原来一个个click变为tap,这样的话 ...

  6. 读jQuery源码 - Deferred

    Deferred首次出现在jQuery 1.5中,在jQuery 1.8之后被改写,它的出现抹平了javascript中的大量回调产生的金字塔,提供了异步编程的能力,它主要服役于jQuery.ajax ...

  7. 读 Zepto 源码之内部方法

    数组方法 定义 var emptyArray = [] concat = emptyArray.concat filter = emptyArray.filter slice = emptyArray ...

  8. 读 zepto 源码之工具函数

    Zepto 提供了丰富的工具函数,下面来一一解读. 源码版本 本文阅读的源码为 zepto1.2.0 $.extend $.extend 方法可以用来扩展目标对象的属性.目标对象的同名属性会被源对象的 ...

  9. 读 Zepto 源码之神奇的 $

    经过前面三章的铺垫,这篇终于写到了戏肉.在用 zepto 时,肯定离不开这个神奇的 $ 符号,这篇文章将会看看 zepto 是如何实现 $ 的. 读Zepto源码系列文章已经放到了github上,欢迎 ...

随机推荐

  1. 打包JavaFX11桌面应用程序

    打包JavaFX11桌面应用程序 这是JavaFX系列的第二弹,第一弹在这里 在第一弹中,我们使用的是OpenJDK8,但是OpenJDK8和Oracle Java JDK不一样,它没有内置JavaF ...

  2. CF516D Drazil and Morning Exercise

    cf luogu 首先每个点到最远点的距离可以预处理出来,这个距离显然是这个点到树直径两端点的最大值.把那个距离记为\(d_i\),然后从小到大枚举\(d_i\),并强制它为最大的\(d_i\),那么 ...

  3. kali安装dnsdict6

    https://src.fedoraproject.org/lookaside/pkgs/thc-ipv6/thc-ipv6-2.7.tar.gz/2975dd54be35b68c140eb2a6b8 ...

  4. LeetCode 腾讯精选50题--有效的括号

    根据题意,第一反应就是使用栈,左右括号相匹配,则将左括号出栈,否则将左括号入栈. 这里我用数组配合“指针”模拟栈的入栈与出栈操作,初始时指针位置指向0,表示空栈,凡遇上左括号则直接入栈,若遇上有括号, ...

  5. MySQL的基本操作一

    本文主要涉及到的SQL知识点包括CREATE创建数据库和表.INSERT插入数据.SUM()求和.GROUP BY分组.DATE_FORMAT()格式化日期.ORDER BY排序.COUNT()统计行 ...

  6. 记录--js中出现的数组排序问题

    这是今天在写vue项目时发生的一个小问题,在此记录一下,方便自己的回顾.项目是前后端分离的,前台主要使用了vue-cli3.0 + mintui,是一个移动端的web app包括了后台发布管理的一些功 ...

  7. 怎么处理Win7系统备份还原提示代码0x80042302的错误?

    我们都知道Win7系统自带备份还原功能,可以在电脑遇到小问题时通过还原至之前备份的正常系统来解决,非常的方便.但是有些用户在使用备份还原功能时,系统会提示0x80042302错误,这该怎么办呢?下面好 ...

  8. python函数:函数使用原则、定义与调用形式

    一.函数初始 二.函数的使用原则 三.函数的定义与调用形式 四.函数的返回值 五.函数参数的使用 一.函数初始 # 须知一: # 硬盘空间无法修改,硬盘中的数据更新都是用新的内容覆盖旧的内容 # 内存 ...

  9. Asp.Net Zero通用打印实现

    Asp.Net Zero是一款非常优秀的web框架,可以用来快速构建业务系统.框架满足了业务系统所需的大部分通用功能,但是系统必须的打印报表功能一直没有实现.下面给大家介绍如何在zero中集成打印功能 ...

  10. 《Python基础教程》第四章:字典

    字典中的值没有特殊的顺序 电话号码(以及其他可能以0开头的数字)应该表示为数字字符串,而不是整数 dict函数可以通过序列对建立字典 clear方法清除字典中所有的项.这是个原地操作,无返回值 get ...