redux源码随便读了读

redux源码浅析

image


祭出我多年珍藏的redux flow 你看这个redux又小又牛逼 respect skr~

如果你之前接触过redux应该看过无数次这个图了吧,组件触发一个动作,ActiconCreators生成一个action通过dipatch方法经过reducers处理返回新的state到store再触发重新render。


而今天讲的不光包括工作流程,还有redux初始化过程,以及react和redux的配合使用。


看源码先提几个问题,然后带着问题看源码是怎么解决的?

  1. 为什么使用redux?
  2. redux怎么管理state?
  3. redux是怎么触发重新render的?

为什么使用redux?

在我另一篇对比redux和mobx对比并没有讲到~(皮一下狠开心)。redux的产生必然他的原因,写个轮子自然有他要跑的道路,如果你不知道redux是来干什么,说明你还没遇到ta解决的问题,或者你遇到了并不知道redux可以拿来解决。

记得我刚开始学这个的时候纯靠记忆来写,不知所以然,再加上有同事把模版代码写好,那我更不用动脑,无脑虚区~。然而真正遇到问题时,解决起来是非常慢速和困难的。

下面我画一张图来解释redux的好处
image

如果你遇到这种场景,组件套组件层级很深的话,想与旁边的同级元素进行交互时,想象一下没有redux,纯react来做的话是不是需要一层层通过props传递,不能做跨级操作,让人感到疲惫。那么redux来了,建立一个共有的state,大家需要什么都可以从这里拿,更改了,还会通知拿了的人需要更新了,再也不需要口口相传,直接互联网。

其他两个问题将在源码中一一阐述

源码开始

下面来看一下源码

image

主要就5个文件 代码总共大概20多k(未压缩) 源码不是很多

首先看下index

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
28
29
30
31
32
33
34
35
36
import 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
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
import React from 'react';
import ReactDOM from 'react-dom';
import { Provider } from 'react-redux';
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import reducer from './reducer';
import saga from './saga';
import Page from './';

const root = document.getElementById('app');
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(sagaMiddleware));
sagaMiddleware.run(saga);

ReactDOM.render(
<Provider store={store}>
<Page />
</Provider>,
root
);
if (module.hot) {
module.hot.accept(['.'], () => {
// eslint-disable-next-line
render(require('.'));
});
}

上面是随便粘了一段业务中的代码,因为讲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,或者在复杂的项目里进行命名空间划分的时候就还要增加很多东西。

好 看下这段代码的执行

image

按照步骤分析

步骤1:combineReducers是个高阶函数,这里看下代码

image

做了一些检测,返回的函数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神秘的面纱。

image

经过一系列判断, enhancer就是之前的applyMiddleware返回的函数,ta译为增强函数,把自己传进去,第二次调用再传进reducer和preloadedState(undefined)。然后applyMiddleware返回的函数再执行createStore。通过了前面判断了,接着执行createStore里定义的dispatch,dispatch才调用了一遍reducer。是不是很绕,那么让我来梳理一个图,你大概就能明白。

总结步骤

image

而combination在每次发送action都会调用。主要在最后两个步骤里,一个dispatch方法里。

image

遗留问题

这里就是之前遗留的两个问题

  1. redux怎么管理state?
  2. 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的机制。有兴趣你可以参考其他的博文了解这两个方法,比你想象的要简单。