为何写这篇文章vue
业余时间我也算看了很多优秀开源项目的源码好比react,redux,vuex,vue,babel,ant-design等,可是不多系统地进行总结,学到的知识很是有限,所以我一直想写一篇完善的源码解读方面的文章。java
第二个缘由是最近面试的过程当中,发现不少候选人对redux的理解很浅,甚至有错误的理解。真正理解redux的思想的人很是好,更不要说理解它其中的精妙设计了。react
所以就有了这篇文章的诞生。面试
REDUX是什么vuex
深刻理解redux以前,首先来看下,redux是什么,解决了什么问题。编程
下面是redux官方给出的解释:redux
Redux is a predictable state container for JavaScript apps.api
上面的概念比较抽象,若是对redux不了解的人是很难理解的。数组
一个更容易被人理解的解释(一样是redux官方的解释):promise
redux是flux架构的实现,受Elm启发
首先科普两个名字,flux和Elm。
下面是facebook官方对flux的解释:
Application Architecture for Building User Interfaces
更具体地说:
An application architecture for React utilizing a unidirectional data flow.
flux是随着react一块儿推出的数据管理框架,它的核心思想是单项数据流。
一图胜千言,让咱们经过图来了解下flux
flux
经过react构建view,而react又是数据驱动的,那么解决数据问题就解决了view的问题,经过flux架构管理数据,使得数据可预测。这样
view也变得可预测。 很是棒~
Elm是一门编译代码到javaScript的语言,它的特色是性能强和无运行时异常。Elm也有虚拟DOM的实现。
Elm的核心理念是使用Model构建应用,也就是说Model是应用的核心。
构建一个应用就是构建Model,构建更新Model的方式,以及如何构建Model到view的映射。
更多关于elm的介绍
了解了上面的东西,你会发现其实redux的任务就是管理数据。redux的数据流能够用下面的图来标示:
redux
redux中核心就是一个单一的state。state经过闭包的形式存放在redux store中,保证其是只读的。若是你想要更改state,只能经过发送action进行,action本质上就是一个普通的对象。
你的应用能够经过redux暴露的subscribe方法,订阅state变化。若是你在react应用中使用redux,则表现为react订阅store变化,并re-render视图。
最后一个问题就是如何根据action来更新视图,这部分是业务相关的。redux经过reducer来更新state,关于reducer的介绍,我会在后面详细介绍。
它精妙的设计咱们在后面进行解读。
最小化实现REDUX
学习一个东西的最好方法就是本身写一个。好在redux并不复杂,从新实现一个redux并不困难。redux源码也就区区200行左右。
里面大量使用高阶函数,闭包,函数组合等知识。让代码看起来更加简短,结构更加清晰。
咱们来写一个"redux"
吧
咱们要实现的redux主要有以下几个功能:
获取应用state
发送action
监听state变化
让咱们来看下redux store暴漏的api
1const store = {
2 state: {}, // 全局惟一的state,内部变量,经过getState()获取
3 listeners: [], // listeners,用来诸如视图更新的操做
4 dispatch: () => {}, // 分发action
5 subscribe: () => {}, // 用来订阅state变化
6 getState: () => {}, // 获取state
7}
咱们来实现createStore,它返回store对象,
store的对象结构上面已经写好了。createStore是用来初始化redux store的,是redux最重要的api。
咱们来实现一下:
createStore
1const createStore = (reducer, initialState) => {
2 // internal variables
3 const store = {};
4 store.state = initialState;
5 store.listeners = [];
6
7 // api-subscribe
8 store.subscribe = (listener) => {
9 store.listeners.push(listener);
10 };
11 // api-dispatch
12 store.dispatch = (action) => {
13 store.state = reducer(store.state, action);
14 store.listeners.forEach(listener => listener());
15 };
16
17 // api-getState
18 store.getState = () => store.state;
19
20 return store;
21};
经过上面的20行左右的代码已经实现了redux的最基本功能了,是否是很惊讶?咱们下面来试下。
咱们如今能够像使用redux同样使用了咱们的"redux"
了。
如下例子摘自官网
你能够把下面这段脚本加上咱们上面实现的"redux"
,拷贝到控制台执行,看下效果。是否和redux官方给的结果一致。
1// reducer
2function counter(state = 0, action) {
3 switch (action.type) {
4 case 'INCREMENT':
5 return state + 1
6 case 'DECREMENT':
7 return state - 1
8 default:
9 return state
10 }
11}
12
13let store = createStore(counter)
14
15store.subscribe(() =>
16 console.log(store.getState())
17)
18
19
20store.dispatch({ type: 'INCREMENT' })
21// 1
22store.dispatch({ type: 'INCREMENT' })
23// 2
24store.dispatch({ type: 'DECREMENT' })
25// 1
能够看出咱们已经完成了redux的最基本的功能了。若是须要更新view,就根据咱们暴漏的subscribe去更新就行了,这也就解释了 redux并非专门用于react的,以及为何要有react-redux这样的库存在。
为了方便各个阶段的人员可以看懂,我省略了applyMiddleware的实现,可是不要担忧,我会在下面redux核心思想章节进行解读。
REDUX核心思想
redux的核心思想出了刚才提到的那些以外。
我的认为还有两个东西须要特别注意。
一个是reducer, 另外一个是middlewares
reducer能够说是redux的精髓所在。咱们先来看下它。reducer被要求是一个纯函数。
被要求很关键,由于reducer并非定义在redux中的一个东西。而是用户传进来的一个方法。
平常工做咱们也会用到reduce函数,它是一个高阶函数。reduce一直是计算机领域中一个很是重要的概念。
reducer和reduce名字很是像,这是巧合吗?
咱们先来看下reducer的函数签名:
1fucntion reducer(state, action) {
2 const nextState = {};
3 // xxx
4 return nextState;
5}
再看下reduce的函数签名
1[].reduce((state, action) => {
2 const nextState = {};
3 // xxx
4 return nextState;
5}, initialState)
能够看出两个几乎彻底同样。最主要区别在于reduce的须要一个数组,而后累计变化。
reducer则没有这样的一个数组。
更确切地说,reducer累计的时间
上的变化,reduce是累计空间
上的变化。
如何理解reducer是累计时间
上的变化?
咱们每次经过调用dispatch(action)的时候,都会调用reducer,而后将reducer的返回值去更新store.state。
每次dispatch的过程,其实就是在空间上push(action)的过程,相似这样:
1[action1, action2, action3].reduce((state, action) => {
2 const nextState = {};
3 // xxx
4 return nextState;
5}, initialState)
所以说,reducer实际上是时间上的累计,是基于时空的操做。
关于middleware的概念咱们很少介绍,
感兴趣能够访问这里查看更多信息。
以下能够实现一个redux的middlewares:
1store.dispatch = function dispatchAndLog(action) {
2 console.log('dispatching', action)
3 let result = next(action)
4 console.log('next state', store.getState())
5 return result
6}
上述代码会在dispatch先后进行打印信息。相似的middleware咱们能够写不少。
好比咱们还定义了另外几个类似的中间件。
咱们须要将多个中间件按照必定顺序执行:
1// 用reduce实现compose,很巧妙。
2function compose(...funcs) {
3 if (funcs.length === 0) {
4 return arg => arg
5 }
6
7 if (funcs.length === 1) {
8 return funcs[0]
9 }
10
11 return funcs.reduce((a, b) => (...args) => a(b(...args)))
12}
13
14// applyMiddleware 的源码
15function applyMiddleware(...middlewares) {
16 return createStore => (...args) => {
17 const store = createStore(...args)
18 let dispatch = () => null;
19 let chain = [];
20
21 const middlewareAPI = {
22 getState: store.getState,
23 dispatch: (...args) => dispatch(...args)
24 }
25 chain = middlewares.map(middleware => middleware(middlewareAPI))
26 // 将middlewares组成一个函数
27 // 也就是说就从前到后依次执行middlewares
28 dispatch = compose(...chain)(store.dispatch)
29
30 return {
31 ...store,
32 dispatch
33 }
34 }
35}
36
37// 使用
38let store = createStore(
39 todoApp,
40 // applyMiddleware() tells createStore() how to handle middleware
41 applyMiddleware(logger, dispatchAndLog)
42)
上面就是redux关于middleware的源码,很是简洁。可是想要彻底读懂仍是要花费点心思的。首先redux经过createStore生成了一个原始的store(没有被enhance),而后最后将原始store的dispatch改写了,在调用原生的reducer之间,插入中间件逻辑(中间件链会顺序依次执行). 代码以下:
1function applyMiddleware(...middlewares) {
2 return createStore =>
3 (...args) => { const store = createStore(...args);
4 // let dispatch = xxxxx; return { ...store, dispatch } }
5}
而后咱们将用户传入的middlewares顺序执行,这里借助了compose,compose是函数式编程中很是重要的一个概念,他的做用就是将多个函数组合成一个函数,compose(f, g, h)()最终生成的大概是这样:
1function(...args) { f(g(h(...args))) }
所以chain大概长这个样子:
1chain = [
2 function middleware1(next) {
3 // 内部能够经过闭包访问到getState和dispath },
4 function middleware2(next) {
5 // 内部能够经过闭包访问到getState和dispath },
6... ]
有了上面compose
的概念以后,咱们会发现每一個middleware的input 都是一个参数next为的function,第一个中间件访问到的next其实就是原生store的dispatch。代码为证:dispatch = compose(...chain)(store.dispatch)
。从第二个中间件开始,next其实就是上一个中间件返回的 action => retureValue 。有没有发现这个函数签名就是dispatch的函数签名。output是一個参数为action的function, 返回的function签名为 action => retureValue 用來做为下一个middleware的next。这样middleware就能够选择性地调用下一個 middleware(next)。社区有很是多的redux middleware,最为经典的dan本人写的redux thunk,核心代码只有两行
, 第一次看真的震惊了。从这里也能够看出redux 的厉害之处。基于redux的优秀设计,社区中出现了不少很是优秀的第三方redux中间价,好比redux-dev-tool, redux-log, redux-promise 等等,有机会我会专门出一个redux thunk的解析。
总结
本篇文章主要讲解了redux是什么,它主要作了什么。而后经过不到20行代码实现了一个最小化的redux。最后深刻讲解了redux的核心设计reducer和middlewares。
redux还有一些很是经典的学习资源,这里推荐redux做者本人的getting started with redux和You Might Not Need Redux。学习它对于你理解redux以及如何使用redux管理应用状态是很是有帮助的。