redux源码浅析
祭出我多年珍藏的redux flow 你看这个redux又小又牛逼 respect skr~
如果你之前接触过redux应该看过无数次这个图了吧,组件触发一个动作,ActiconCreators生成一个action通过dipatch方法经过reducers处理返回新的state到store再触发重新render。
而今天讲的不光包括工作流程,还有redux初始化过程,以及react和redux的配合使用。
看源码先提几个问题,然后带着问题看源码是怎么解决的?
- 为什么使用redux?
- redux怎么管理state?
- redux是怎么触发重新render的?
为什么使用redux?
在我另一篇对比redux和mobx对比并没有讲到~(皮一下狠开心)。redux的产生必然他的原因,写个轮子自然有他要跑的道路,如果你不知道redux是来干什么,说明你还没遇到ta解决的问题,或者你遇到了并不知道redux可以拿来解决。
记得我刚开始学这个的时候纯靠记忆来写,不知所以然,再加上有同事把模版代码写好,那我更不用动脑,无脑虚区~。然而真正遇到问题时,解决起来是非常慢速和困难的。
下面我画一张图来解释redux的好处
如果你遇到这种场景,组件套组件层级很深的话,想与旁边的同级元素进行交互时,想象一下没有redux,纯react来做的话是不是需要一层层通过props传递,不能做跨级操作,让人感到疲惫。那么redux来了,建立一个共有的state,大家需要什么都可以从这里拿,更改了,还会通知拿了的人需要更新了,再也不需要口口相传,直接互联网。
其他两个问题将在源码中一一阐述
源码开始
下面来看一下源码
主要就5个文件 代码总共大概20多k(未压缩) 源码不是很多
首先看下index1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36import createStore from './createStore'
import combineReducers from './combineReducers'
import bindActionCreators from './bindActionCreators'
import applyMiddleware from './applyMiddleware'
import compose from './compose'
import warning from './utils/warning'
import __DO_NOT_USE__ActionTypes from './utils/actionTypes'
/*
* This is a dummy function to check if the function name has been altered by minification.
* If the function has been minified and NODE_ENV !== 'production', warn the user.
*/
function isCrushed() {}
if (
process.env.NODE_ENV !== 'production' &&
typeof isCrushed.name === 'string' &&
isCrushed.name !== 'isCrushed'
) {
warning(
'You are currently using minified code outside of NODE_ENV === "production". ' +
'This means that you are running a slower development build of Redux. ' +
'You can use loose-envify (https://github.com/zertosh/loose-envify) for browserify ' +
'or setting mode to production in webpack (https://webpack.js.org/concepts/mode/) ' +
'to ensure you have the correct code for your production build.'
)
}
export {
createStore,
combineReducers,
bindActionCreators,
applyMiddleware,
compose,
__DO_NOT_USE__ActionTypes
}
主要是把其他文件整合导出,这里没什么可讲。其他文件我也不想一个个讲,因为串不起来就记不住。
1 | import React from 'react'; |
上面是随便粘了一段业务中的代码,因为讲redux 中间件saga
可以忽略掉,缘起缘灭就是entry的代码。
缘起缘灭
最主要的一行是这一行1
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
还记得我前面说的主要的五个文件createStore
,combineReducers
,bindActionCreators
,applyMiddleware
,compose
这就用到了俩。其实是三个
因为reducer
这个参数其实是1
2
3
4
5// reducer
...
export default combineReducers({
reducer
});
那么combineReducers
也用上了
其他两个一个会在中间价处用到(compose
),其实是一个柯里化函数,跟redux本身没什么关系。另一个是为action和dispach方便调用的语法糖函数(bindActionCreators
)
这样一看是不是觉得redux文件很少十分的简单呢,其实不然,而且如果要配合react使用,还有高效的发送action,或者在复杂的项目里进行命名空间划分的时候就还要增加很多东西。
好 看下这段代码的执行
按照步骤分析
步骤1:combineReducers
是个高阶函数,这里看下代码
做了一些检测,返回的函数combination
是每次发action时,reducer执行处理的地方,现在初始化还没用到这个combination
,等下跑起来的时候再细讲。
步骤2:applyMiddleware
这个函数还是很有意思的,他也是一个高阶函数,但不同于combineReducers
的是,层级深了一层,也就是说1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
```js
import compose from './compose'
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
}
}
}
applyMiddleware
这个高阶函数作为参数,执行之后返回了又一个高阶函数作为参数,而这个高阶函数还需要createStore作为参数,目的是拿到store的getState方法,还有dispatch这两个作为参数传给每一个中间件,让他们各自完成自己想要的封装。
可是他的设计者为什么要搞的那么花了呼哨呢?如果我面对这个需求的话,我可能不需要applyMiddleware
这个高阶函数,把middlewares直接传给createStore
,这样createStore
中,getState方法还容易拿到,这些中间件的捆绑操作交给createStore
,岂不是更容易让人理解吗?
这里不难发现,如果你知道函数式编程的话,或者这里是什么设计模式,我还未了解。他用了函数式编程的方式,让代码更容易测试,更加健壮。关键在于,如果按照我之前说的方式来执行middlewares,首先你的中间件是不确定有多少个的,那么对于传参的时候,redux的用户是非常不爽的,createStore
要传多种多个参数,比如createStore(reducer, initstate, middleware1, middleware2)
这种设计上是不可取的,那你说middleware可以是个一个数组啊,比如createStore(reducer, initstate, [middleware1, middleware2])
。emm这块还没想到怎么反驳。。。。或许根本反驳不了,就是多种实现方式,各有取舍,相比之下的选择而已。
我其实想学会redux在设计applyMiddleware
这个的实现方式,想在下一次我遇到这种场景去使用到,但是我或许还没领悟其中的精髓,就算知道怎么实现了,下一次我想我不会活学活用,还是会使用我熟悉的方式,打破固有思维还是很难得,但是打破了就是成长。我的理解只到他是函数式编程,只不过复杂了一些,createStore
调用了自己而已。
到这里,createStore
里的参数都执行了一遍,又返回了各自的函数,接下来才是要执行createStore,让我们揭开createStore
神秘的面纱。
经过一系列判断, enhancer就是之前的applyMiddleware
返回的函数,ta译为增强函数,把自己传进去,第二次调用再传进reducer和preloadedState(undefined)。然后applyMiddleware
返回的函数再执行createStore
。通过了前面判断了,接着执行createStore
里定义的dispatch,dispatch才调用了一遍reducer
。是不是很绕,那么让我来梳理一个图,你大概就能明白。
总结步骤
而combination在每次发送action都会调用。主要在最后两个步骤里,一个dispatch方法里。
遗留问题
这里就是之前遗留的两个问题
- redux怎么管理state?
- redux是怎么触发重新render的?
当前的state都是通过reducer执行处理来的,而listener这个函数里其实做的是this.setState操作,就可以达到利用react自己触发重新render,也比较好解释react-redux这个库中提供的provider和connect做了些什么,provider其实就是一个react组件,做了提供listener,为listener放入了this.setState().我们经常看到provider放在组件的最上层,也是这个原因,一旦state改变,所有connect的子组件都会判断刷新,如果connect的子组件里的props更新了,那么就会告知刷新,否则不会刷新。这是因为connect是个高阶组件,他会在shouldComponentUpdate
生命周期判断是否需要刷新。
那么为什么redux是怎样直接传递props给深层级的组件呢,其实redux自己是做不到的。而是provider接收Redux的store作为props,通过context对象传递给子孙组件上的connect。
当然你也可以直接用creatStore的subscribe来监听组件,react-redux只不过是redux配合react的非常好用的工具,subscribe这里redux用了观察者模式,这是js领域经常用到的设计模式。这里就不多讲了。
compose和bindActionCreator这里也不讲了,我觉得我上面简单提到的足以解释两个方法,并不影响你理解redux的机制。有兴趣你可以参考其他的博文了解这两个方法,比你想象的要简单。