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. 【原创】大数据基础之ETL vs ELT or DataWarehouse vs DataLake

    ETL ETL is an abbreviation of Extract, Transform and Load. In this process, an ETL tool extracts the ...

  2. 【zhifu】web开发中的支付宝支付和微信支付

    一.支付类型: 支付宝支付: 支付宝app内的网页支付: app外(即普通浏览器)网页支付: 微信支付: 微信app内的支付(在这里叫公众号支付) app外的支付(微信H5支付): 微信公众号的支付宝 ...

  3. centos7安装配置LVS+keepalived高可用

    Centos7-Lvs+Keepalived架构 LVS+Keepalived 介绍 1 .   LVS LVS 是一个开源的软件,可以实现 LINUX 平台下的简单负载均衡. LVS 是 Linux ...

  4. vue打包后css背景图片地址找不到

    背景图片变成了这样:static/css/static/imgs/xxx.jpg 解决方法,修改build/utils,添加   publicPath: '../../'    就行 对比了下,com ...

  5. vue项目中实现图片懒加载的方法

    对于图片过多的页面,为了加速页面加载速度,所以很多时候我们需要将页面内未出现在可视区域内的图片先不做加载, 等到滚动到可视区域后再去加载.这样子对于页面加载性能上会有很大的提升,也提高了用户体验. 实 ...

  6. Delphi 10.3.2来了!

    昨晚,官方正式发布了Delphi 10.3.2,增加对Mac 64应用的开发,支持Linux桌面开发,这个是通过集成fmxlinux实现的,同时修正400个bug,编译器,102个ide,84个fmx ...

  7. Oracle 触发器学习笔记一

    触发器名:触发器对象的名称.由于触发器是数据库自动执行的,因此该名称只是一个名称,没有实质的用途.触发时间:指明触发器何时执行,该值可取:before:表示在数据库动作之前触发器执行;after:表示 ...

  8. linux常用查看命令

    一般查看 cat -n 文件名 n 代表显示行号 分页查看 more 文件名 more -50 文件名 -50 代表一页显示50行 向下翻页  空格 向上翻页 b more +/搜索值 文件名 查找文 ...

  9. unzip解压3G或者4G以上文件失败的解决方法

    Linux下,使用unzip解压时,报错:End-of-central-directory signature not found.  Either this file is nota zipfile ...

  10. aaa服务器

    验证 授权 记账 authentication\authorization.accounting