对使用Redux和Redux-saga管理状态的思考

原文地址在我的博客, 转载请注明出处,谢谢!

概述

本文介绍了对 Redux 状态管理的思想、原理、架构方法的认识和思考以及配合redux-saga处理异步操作的实践

前言

You know, React 只是属于MV*架构模式的 view 层,是一种状态机,只使用 React 难以控制大型、复杂的应用,它需要一些框架来帮助管理状态,因此如何有效、简单、易于测试地管理这个状态机是各种架构框架感兴趣的。Facebook 早就意识到这个问题并提出了 Flux 架构,比较复杂; 后来出现了 Redux、Mobx 等。MobX 可以处理简单数据流的场景,可以实现精确更新; Redux 是从 Flux 和其他框架借鉴了一些思想, 它比 Flux 简单、易于理解、用于处理复杂数据流,并具有很强的扩展性,社区诞生了像redux-thunk、redux-promise、redux-saga等中间件用于方便地处理异步操作。最近也在项目中使用了 Redux 及其中间件 redux-saga 来管理状态和处理异步操作,这篇文章就来谈谈我对它们的思考和实践。

正文

Redux 思想

先来谈谈背景(需求)

我觉得理解Redux的思想,谈谈MV*架构模式的思路也许会有帮助。

MV*架构模式,它们的核心都是职责分离、解耦,不同的层次做不同的事,能让一个复杂、混乱的应用变得思路清晰,代码可以复用,并且易于测试,有利于分工合作,构建更大、更复杂的应用。

一个应用要包括哪些功能?表现(view)、处理数据的逻辑(model)以及数据映射到表现层的逻辑(presenter or controller),数据在这三层之间流通(MVVM模式通过数据双向绑定实现view和model同步)。

React 根据state来render,它只是个状态机,并没有解决管理状态的问题。我们在单纯的使用React来写组件的时候,经常会遇到组件间通信和管理组件state的问题,前者常用的解决办法就是把数据提到父组件共享;后者管理state简单的情况还行,一复杂就很麻烦且容易出错,再遇到一些需要异步处理的操作,想想就头皮发麻。

当你开发中遇到一些反人类的操作时,试着去想如何改变一下思路让它变得更简单,别耐着性子安慰自己开发就是这样 :)

解决方案

Redux 正是用来解决大型React应用所面临的状态管理、数据流通、异步处理、测试、团队合作等问题:

Redux 用单一的object tree来表示整个应用的state,这个表示state的对象树被放在唯一的store 中,state相当于store的快照;所有组件都会通过API拿到这个state,各取所需;

Redux 把页面上用户的操作或者浏览器的行为(如路由的变化)定义为一个要更新state的action,这个action是一个普通对象,它包含了要执行动作的类别和传递到state的数据(如果有的话),它只表明要更改state的意图,相当于一个信号,并不能直接修改state,Redux会集中处理这些信号,这个action由你来决定何时发起;

定义好信号,你还需要根据不同的信号定义不同的逻辑函数(reducers)来更新state。

通过这张图来整理一下:

“redux 原理图”

咳咳...比如用户点击的一个按钮,你在按钮上绑定的回调函数调用了一个(多个)action creator,action creator就返回了一个更新state局部数据的action,store会根据这个(多个)action找到对应的reducers(reducer需要做拆分),按照action发起的顺序依次执行来更新state,每个reducer只负责更新自己关心那部分,根 reducer 把多个子 reducer 输出合并成一个单一的 state 树,生成一个新的state保存在store中,store中的state可以通过相应API传递到子组件。

这就是整个数据流。

那Redux如何处理异步操作?

Redux借鉴了中间件思想,利用可扩展的中间件来改造dispatch函数。比如redux-thunk让dispatch不仅仅可以接收action,还可以接受函数作为参数,你可以在这个函数里完成异步操作。再如redux-saga更强大、也更复杂,在后面会讲到。

Redux 架构方法

对于React技术栈,Redux实现了react-redux库来让Redux管理React应用(其他框架也有相应的库),里面集成了一些有用的函数来把一些明确的流程自动化,如createStore用于创建唯一store,可以把根reducer传进createStore使store自动调用对应reducer,可以扩展中间件;提供<Provider store>组件和connect高阶组件用来包裹render component并传递state,connect还能自动dispatch,让你只要调用action creator就能dispatch;提供combineReducers来组合分割的reducers等。

知道这些特性,就可以配合react-router构建大型应用了:

总的思路就是:利用react-router 把应用分割为各个页面,reducer、action creator也跟随页面分割而分割。每个路由对应的页面下都有components和containers,分别存放functional components 和class components,前者用来渲染,后者当做containers被connect包裹,containers包裹components;containers从connect得到state并映射需要的数据到子组件的props,子组件再向下传递。

具体如何构建React + Redux + react-router,我在另一篇博客里讲了。

使用这种架构,开发大型应用变得得心应手。

Redux 存在的问题

但是当我深入项目开发的时候,也逐渐发现了一些问题:

  • 这种架构项目结构不够扁平化,文件嵌套比较深,思路比较复杂,搭建、写起来比较麻烦,上手有难度;
  • 由于所有action creator都定义在页面层次上,让子组件调用必须一层一层的传递,很麻烦且非常容易出错,也很难调试;
  • state难以做到局部更新(这个可以用reselect
  • Redux只是传递了一种思路,定义了几个简单的API,很灵活,架构方式不固定,设计方式不固定(如:如何设计state树)但这也是它的缺点,新人往往看完一遍还是不知道怎么做,对新人不友好

总之,redux可以胜任复杂数据流的应用,但是也比较难,前期架构比较麻烦,适合有经验的人。

使用redux-saga处理异步操作

Redux 倡导action 和reducer尽可能"纯净",没有什么“副作用”。可是像一些异步操作比如获取数据是必须的,在哪处理这些副作用呢?redux 把这些"不纯净的"任务交给了中间件,通过 向createStore里应用中间件,在交由store处理action之前就可以对其完成一些其他的操作:

“redux 中间件”

而redux-saga 是Redux一个强大但并不复杂的用于异步处理的中间件。

它的思路是什么?相比其他redux异步中间件如redux-thunk、redux-promise有什么不同?

先看名字来理解:saga,这个术语常用于CQRS架构,代表查询与责任分离

没错,就是查询(dispatch)与责任(sagas)分离。saga提供了action监听函数,只需在组件里dispatch 相应type的action,就可以自动调用你定义好的对应这个action的异步处理函数(sagas)来完成任务,保证了只在组件里dispatch action来发起异步操作而不是redux-thunk、redux-promise的调用action creators。

另外一大特色就是redux-saga做到了异步代码以同步方式写,非常直观方便,怎么做到的呢?它是利用了ES6新魔法Generator迭代器,可以完美解决异步回调地狱,让你以同步方式写异步。saga正是利用Generator特性让其处理异步变得非常方便又容易理解。这是一个常见的请求后台数据的异步操作,感受一下:

function *fetchNodeDetailByNodeId({ payload: { nodeId } }, { call, put }) {
      try {
        const { data, status }= yield call(fetchNodeDetailByNodeId, nodeId)
        if (data && status.errmsg === 'success') {
          yield put({
            type: 'setStates',
            payload: {
              nodeDetailData: data,
            },
          });
        } else {
          message.info('开了个小差,再试一次吧..');
        }
      } catch (error) {
        console.log(error);
      }
    },

call 和 put 是saga的API,相当于dispatch,但是并不是真正执行dispatch,只是发送你指定的指令,交由saga中间件来执行这个指令。这样看来,这个saga函数就是一些指令的集合,称为effects,副作用,用来描述任务

为啥要描述指令而不直接调用呢?这样是因为易于测试,如果直接调用,你还得模拟调用的函数,详见redux-saga文档。

我觉得redux-saga相比于其他中间件的优点:

  • 查询与责任分离,保证了action的纯洁性,符合redux设计思想
  • 实现以同步方式写异步操作,容易理解,逻辑清晰
  • 通过发送指令而不是直接调用让异步操作变得容易测试
  • 监听、执行自动化
  • 提供了丰富强大的指令来完成复杂的操作,比如无阻塞调用,同时执行多个任务等

讲道理,任何redux异步操作都可以让saga这个中间件来完成,非常复杂的同样可以胜任,并且很容易理解(异步操作以同步方式写)和测试。再配合dva,可以减轻redux的复杂度同时完成更强大的功能。

这样以来,redux配合saga,就可以让它们各司其职,整个思路也变得清晰起来:

redux 倡导action和reducer要纯洁,那就让所有异步操作这些不纯洁的任务交给saga,reducer不用变,还是纯函数;定义好对应action的sagas专门用来处理异步操作,我只要在组件需要的地方里dispatch 纯action就行了,符合redux设计思想。

总结

使用redux来管理应用状态适用于复杂的应用,而复杂的应用会有复杂的异步处理,异步处理不要用redux的action creator,它不是用来做这个的,也违背了redux设计思想,redux把这些任务交给了异步中间件,应该由它们来完成。使用redux saga是一个推荐的选择,它懂redux,也懂你需要什么。另外,既然你用到了saga,不妨试试dva架构,5分钟上手,值得一试。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,362评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,330评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,247评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,560评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,580评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,569评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,929评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,587评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,840评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,596评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,678评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,366评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,945评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,929评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,165评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,271评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,403评论 2 342

推荐阅读更多精彩内容