深入理解redux

640?

为什么写这篇文章


业余时间我也算看了不少优秀开源项目的源码比如react,redux,vuex,vue,babel,ant-design等,但是很少系统地进行总结,学到的知识非常有限,因此我一直想写一篇完善的源码解读方面的文章。

第二个原因是最近面试的过程中,发现很多候选人对redux的理解很浅,甚至有错误的理解。真正理解redux的思想的人非常好,更不要说理解它其中的精妙设计了。

因此就有了这篇文章的诞生。

640?

REDUX是什么


深入理解redux之前,首先来看下,redux是什么,解决了什么问题。

下面是redux官方给出的解释:

Redux is a predictable state container for JavaScript apps.

上面的概念比较抽象,如果对redux不了解的人是很难理解的。

一个更容易被人理解的解释(同样是redux官方的解释):

redux是flux架构的实现,受Elm启发

首先科普两个名字,flux和Elm。

flux

下面是facebook官方对flux的解释:

Application Architecture for Building User Interfaces

更具体地说:

An application architecture for React utilizing a unidirectional data flow.

flux是随着react一起推出的数据管理框架,它的核心思想是单项数据流。

一图胜千言,让我们通过图来了解下flux

640?wx_fmt=other
flux

通过react构建view,而react又是数据驱动的,那么解决数据问题就解决了view的问题,通过flux架构管理数据,使得数据可预测。这样

Elm

Elm是一门编译代码到javaScript的语言,它的特点是性能强和无运行时异常。Elm也有虚拟DOM的实现。

Elm的核心理念是使用Model构建应用,也就是说Model是应用的核心。

更多关于elm的介绍

了解了上面的东西,你会发现其实redux的任务就是管理数据。redux的数据流可以用下面的图来标示:

640?wx_fmt=other
redux

redux中核心就是一个单一的state。state通过闭包的形式存放在redux store中,保证其是只读的。如果你想要更改state,只能通过发送action进行,action本质上就是一个普通的对象。

你的应用可以通过redux暴露的subscribe方法,订阅state变化。如果你在react应用中使用redux,则表现为react订阅store变化,并re-render视图。

最后一个问题就是如何根据action来更新视图,这部分是业务相关的。redux通过reducer来更新state,关于reducer的介绍,我会在后面详细介绍。

它精妙的设计我们在后面进行解读。

640?

最小化实现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对象,

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核心思想章节进行解读。

640

REDUX核心思想


redux的核心思想出了刚才提到的那些之外。

reducer 和 reduce

reducer可以说是redux的精髓所在。我们先来看下它。reducer被要求是一个纯函数

  • 被要求很关键,因为reducer并不是定义在redux中的一个东西。而是用户传进来的一个方法。

  • - 纯函数也很关键,reducer应该是一个纯函数,这样state才可预测(这里应证了我开头提到的Redux is a predictable state container for JavaScript apps.)。

日常工作我们也会用到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累计的时间上的变化,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其实是时间上的累计,是基于时空的操作。

middlewares

关于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的解析。

640?

总结


本篇文章主要讲解了redux是什么,它主要做了什么。然后通过不到20行代码实现了一个最小化的redux。最后深入讲解了redux的核心设计reducer和middlewares。

redux还有一些非常经典的学习资源,这里推荐redux作者本人的getting started with redux和You Might Not Need Redux。学习它对于你理解redux以及如何使用redux管理应用状态是非常有帮助的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。 经导师精心指导并认可、获 98 分的毕业设计项目!【项目资源】:微信小程序。【项目说明】:聚焦计算机相关专业毕设及实战操练,可作课程设计与期末大作业,含全部源码,能直用于毕设,经严格调试,运行有保障!【项目服务】:有任何使用上的问题,欢迎随时与博主沟通,博主会及时解答。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值