React进阶(1)-理解Redux

前言

image

在React中,数据流是单向的,并且是不可逆的,这其实,也很好理解,之所以这么设计,是因为组件复用的特点

父(外部)组件向子(内部)组件传递数据是通过自定义属性props值的方式进行实现的,并且在子组件内部通过this.props进行获取,它并不能直接被修改,如果想要修改,那么得通过React内置的一个setState的方法进行触发

而子组件想要传递数据给父组件,是通过调用父组件的方法进行通信

一个组件可能存在着很多状态,组件之间有时需要进行通信,对于多个组件状态维护,如果依旧用原来的方式,那么就比较复杂了的

那么Redux正好解决了这一问题.个人觉得,Redux学起来很抽象,的确是块硬骨头,但是高山始终是要越过的

下面就一起来学习下Redux的

您将在本文中学习到

  • Redux是什么

  • Redux的使用场景以及与不使用Redux的灵魂对比

  • Redux的工作流程

  • Redux的设计基本原则

本篇虽不涉及代码层面上的,但是对后续编码Redux非常重要,磨刀不误砍柴工

如果想阅读体验更好,可戳链接,React进阶(1)-理解Redux

Redux是什么?

官方解释:JavaScript应用程序的可预测的状态容器(一个管理应用程序状态的框架)

通俗一点:管理组件公共数据状态的容器(仓库/区域)

解决的问题: 当应用组件拥有多个状态,并且组件之间需要共享数据状态时,从原始的组件传递数据的方式中解脱出来,集中管理组件的状态

你可以把Redux理解为一个仓库,房产中介.拥有很多共享的房源的一个管理者,后面会有具体的例子

Redux的使用场景

从上面提到的Redux解决问题可以看出,Redux只是用来管理和维护组件的状态的

React开发的模式就是组件化开发,将一个大的应用拆分成若干个小的应用,然后拼接成一个大的应用,而编写一个大小应用就是在编写各个大小组件

而组件的显示形态又取决于它的状态,这不区分于无论是外部的props还是内部的state,而组件之间有时需要共享传递数据,Redux仅仅就是用来管理这些组件的状态的

在一些开发者眼里,项目里要是没有用到Redux,就觉得很low,要么把Redux捧得高高在上,要么说都已经快0202年了,都用React hook了,鄙视得不行,个人觉得完全没有必要.

React与Redux本身就是解决两个不同方向的问题,某种程度上讲,React可以视为MVC架构中的视图层V,而Redux则是model数据层M,而C层往往是连接视图层和model的连接器,往往处理前端数据请求,路由跳转等业务逻辑

即使不用Redux,照样能做小应用,只是略复杂繁琐一些而已,下面会介绍他们之间的对比

那么对于技术选型,什么时候用Redux什么时候不用?

以下是选用Redux的场景:

  • 项目非常庞大,公共组件与业务组件非常多,用户的使用方式比较复杂

  • 不同身份的用户角色权限管理(例如很多后台管理系统,普通用户,超级管理员,VIP用户)读,写权限管理等

  • 多个用户之间可以协作实时操作(很多那种在线敏捷协作办公文档工具,多个用户可以实时编辑操作同一份文档等的,例如石墨文档,语雀,confluence.钉钉等的)

  • 需要与服务器大量的交互,或者使用了webscoket的,聊天,直播等应用的

  • 视图层view需要从多个来源获取数据

....只要你发现React解决不了的问题,遇到多交互,多数据源的,那么就可以考虑使用Redux的

反之,则以下则是没有必要使用Redux

  • UI层非常简单,只是用于渲染,无复杂的数据交互,依赖外部的props就可以渲染组件

  • 用户的使用方式比较简单,页面之间比较独立,没有互相协作

  • 与服务器之间没有大量交互

当你发现使用React实在解决不了的问题,在各个组件之间传递数据非常复杂,很痛苦时,那么就可以考虑使用Redux了的,只要你hold住,没有所谓的高大上技术,只有适合自己业务的技术

盲目引入Redux只会增加项目的复杂度,引入新的技术应该是循序渐进的

不使用Redux与使用Redux的灵魂对比

下面这张组件树状态图的对比就很好的解释了使用Redux与不使用Redux的区别

使用Redux与不使用Redux.png

一个React应用(例如:pc网站,手机app应用,后台管理系统等用React技术栈构建的应用)其实就是一颗由组件构成的树,如上图所示,在这颗树的根结点,最顶层的组件就是该应用的本身,由于组件都是以树结构组织起来的,当每个组件被渲染时,它都会递归地渲染下级组件
React构建的应用.png

最顶层的应用根节点.png

假设红色圆圈代表的是一个应用的子组件,如果想要把该红色圆圈组件的状态数据传递给父级或者非父级组件,它是通过调用父组件的方法来实现,这样一层一层往上传,如果组件树很庞大的话,那么就会变得非常繁琐

在小型项目中,Redux并不是必需的,但是使用Redux却是一劳永逸的,管理组件的状态方便得多,对于大型应用来说,单纯使用原始的数据传递方式

那么组件之间的传值会变得非常复杂,如果要做一个大型的应用,那么就需要在React的基础上配置一个数据层的框架进行结合的使用

如果改为右边的Redux处理方式,将红色圆圈组件的状态数据放到一个Store仓库当中集中进行管理,哪个组件需要的话,直接派发给哪个组件就可以了的.

在Redux中,要求把组件的数据放到公共的存储仓库(区域)当中,让组件尽可能的减少状态数据存储,换而言之,所有组件自身内部状态数据都不放在state里面了,把它放到Store这样的一个存储仓库当中去

其实本质上来说,是放到reducer里面去管理,Store从Reducer中拿到返回的数据state,最后供外部组件的取用

当红色圆圈组件想要改变数据传递给其他组件时,只需要去改变Store里面的存储红色圆圈组件的数据就可以了

一旦Store公共存储的状态数据发生改变了的,由于其他组件是公用Store的数据,那么其他组件就会感知到Store的数据发生了改变,从而自身组件也会跟着改变

只要Store公共存储区域的数据发生改变,凡是共用了Store里面的数据的组件都会重新的取数据

这样一来,红色圆圈组件的数据就非常容易的传递给其他组件了,无论是它的父级组件还是兄弟,非兄弟组件的

Redux就是把组件的数据放到一个公共的区域(仓库)中进行存储,当改变Store存储区域里面的数据时,其他组件如果用到了公共区域的数据,那么就会感知到数据的变化,它会自动的更新取Store中最新的数据

这样话,无论你的应用组件嵌套得有多么复杂,多么深,走的流程都是一样的,组件之间并不会干扰,低耦合的效果

当组件一修改,把修改的数据放到Store当中,而其他组件又从Store当中再来取,这样的话,组件与组件之间并不是直接进行通信的,是通过这么一个store中间角色来实现数据的传递共享的.

这样的话,组件的数据传递就简单多了的,也避免了组件与组件之间频繁通信,容易产生混乱的问题

Redux其实是Flux数据框架的一个替代演进,同样强调的是单向的数据源,保持状态只具备读的能力,而数据改变只能通过纯函数完成基本,这和原先中React的UI=render(data)完全吻合.

React与Redux是两个独立的框架,前者是用于组件视图层的渲染,而后者是管理组件的数据

Redux的工作流程

现在已经知道了使用Redux与不使用Redux的区别,那么现在是时候来了解一下Redux的工作流程了,下面这个流程图对于理解Redux很重要 先附上一张Redux工作流的流程图:以后会在代码中逐步的体现

Redux工作流.png

上面的Redux工作流图中,以中间为准:包括了Store,ReactComponents,Actions Creators,以及Reducers

其中Store代表的就是负责组件存储所有公共状态的数据,全局只有一个Store.(这里你可以把它理解为类似生活当中中介公司管理房源的仓库(数据库)的区域经理)

实质上:store就是把Reducer关联到一起的一个对象,它提供dispatch(action)方法更新state,以及getState方法获取state

React Components:指的是页面上的任意一个组件(你可以理解为小区公寓楼里的每个房间,而你就是住在里面的租房用户)

Actions Creators:具体要干什么事情,触发的动作,可以看做一个交互动作,改变应用状态或view的更新,都需要通过触发action来实现,Action的执行结果就是调用Dispatch来处理相应的事情,实现页面视图view的更新,唯一的办法就是调用dispatch派发action

它是一个javascript对象,是用来描述事件的行为的,对象里记录了相关的信息,例如:todolist的添加,删除list的这个具体操作,就是一个action

(当你想要提出换房的时候,跟中介公司管理房源的经理说,你要换个带有沙发,电视,配备厨房的两室一厅的房子,因为增加人口了,现有的房子住不下了的,你要做的什么事情,提出的条件信息就是数据),这个动作可以理解为actions creators

在你提出换房的时候,房产中介公司经理虽然手握很多房源,但是他也没有办法记得所有的房子相关信息,它需要去数据库(仓库)里去查,你常常看到中介小哥带你看房的时候

手上拿一个单子,Excel表格跟你介绍房源的时候,你可以把这个单子,Excel表格理解为一个实时记录本,只要有房子出租去了,这个表格就会实时更新(新旧信息的核实对比),返回一张新的房源信息表单给房产中介的经理

Reducer:可以把上面的用于实时更新记录房源信息的记录本称为Reducer,它只用作于根据旧的房源与提出新的需求(动作),总是会返回一张新的记录本给房产中介经理

实质上:Reducer是根据action发出的type(动作类型)来做什么事(返回最新的state给store等逻辑操作)

现在归纳一下整个流程:

我(租客/组件React Component)想要换一个xxx信息的房子(Actions creators,具体要做的什么事情),房产中介经理收到了请求,他得根据你提供的一些需求信息去找相应的房源信息

但是房源太多,需要借助一个实时的记录本去查看符合条件的房源信息,当查到符合条件的信息后,这个记录本(Reducer)把最新的信息会返回给房产经理(Store),最终把信息返回给用户React Components,实现房子替换的更新

虽然文字啰嗦了点:但是Redux就是这么一回事,我要换大房子,房产中介经理听到后,它去记录本里面去查,查到之后,返回到房产中介经理,然后最终在返回给我,实现房子的替换

那么转换为代码理解:

页面上的一个组件,想要获取更新Store中的数据,跟Store说,我点击这个按钮,要更新这个组件的数据,要干什么事情,做的这个具体动作就是Actions Creators,这时会派发(dispatch) 该动作(action)给Store,Store会去Reducer里面去查一下,Reducer会返回一个新的结果给Store,Store拿到最新的数据结果后,返回给页面上的组件,实现页面组件的更新

大家可以先仔细体会上面这段文字的含义,在后续的实例代码中,在回过头来对比着代码与文字进行理解的,后续还会在拿出来的

Redux的设计基本原则

在Redux中有以下几个设计基本原则

  • 单向数据流

  • 唯一数据源

  • 保持状态只读

  • 数据的改变只能通过纯函数reducer来完成

单向数据流: 这个其实与props不能直接被修改一样,在父组件向子组件传递数据时是通过属性的方式进行传递的,而子组件内部通过this.props进行接收,但是外部传递过来的props属性不能直接被修改,若想要修改,需要借助React内置的setState方法进行触发

唯一数据源: 它指的是组件的应用状态数据应该只存在唯一的Store上,这一点是不同于Flux的,在Flux中允许有多个store。而在Redux中整个组件的应用只保持一个Store,所有组件的数据源就是这个Store上的状态,可以将它Store理解为一个全局的变量对象

保持状态state可读: 不能直接的去修改状态,要修改Store的状态,必须要通过派发(dispatch)一个action对象去完成

然后组件渲染的对应的界面要更改的话,实际更改的就是组件的状态,如果状态都是只能读不能修改的话,那么界面就不会更新变化了

想要更改用户界面的渲染,就要改变组件的应用状态,但时改变组件状态的方法不是直接去修改状态上的值,而是创建一个新的状态对象返回给Redux,由Redux完成新的状态的组装

组件数据的改变只能通过纯函数完成

所谓的纯函数,就是指Reducer,而Redux某种程度上讲,它是Reducer+Flux的组合,其中这Redux的Red代表就是Reducer,而ux就是Flux,但是又不同于Flux,它更像是Flux的一个实现,演进。它是为了描述Action如何改变组件的状态的

这也是为什么Redux这个名称比较抽象的原因,其中Reducer类似一个数组中的迭代器函数reduce

var arr = [1,2,3,4,5,6]
var sum = arr.reduce(function reducer(prevValue, currentValue,index,array){
    console.log(`上一次调用回调返回的值(或者是提供的初始值): ${prevValue},数组中当前被处理的元素: ${currentValue}, 当前元素在数组中的索引: ${index}, 调用的数组: ${array}`);
  return prevValue+currentValue;
},0)
console.log(sum); // 21

VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 0,数组中当前被处理的元素: 1, 当前元素在数组中的索引: 0, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 1,数组中当前被处理的元素: 2, 当前元素在数组中的索引: 1, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 3,数组中当前被处理的元素: 3, 当前元素在数组中的索引: 2, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 6,数组中当前被处理的元素: 4, 当前元素在数组中的索引: 3, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 10,数组中当前被处理的元素: 5, 当前元素在数组中的索引: 4, 调用的数组: 1,2,3,4,5,6
VM1742:3 上一次调用回调返回的值(或者是提供的初始值): 15,数组中当前被处理的元素: 6, 当前元素在数组中的索引: 5, 调用的数组: 1,2,3,4,5,6
VM1742:6 21

上面的代码中是做一个简单的累加,reducer函数接收四个参数,第一个参数是上一次调用返回的结果,第二个参数是当前被处理的元素的值,第三个是当前元素在数组中的索引,第四个是调用的原数组

这个reduce的方法接收一个函数作为累加器,reduce 为数组中的每一个元素依次执行回调函数

而在Redux中,每个reducer纯函数如下所示
其中reducer函数的第一个参数state是指当前的状态值,而第二个参数action是接收到的action对象

而reducer函数要做的事情就是根据state和action的值产生一个新的对象返回给Store,它是定义整个组件应用状态如何更改,根据Action动作行为去更新Store中的状态

注意的是reducer必须是纯函数,换句话说,reducer函数的返回结果必须完全由参数state和action决定,而且不产生任何的副作用,也不能修改参数state和action对象

如下一个典型的reducer示例,reducer只是一个函数名称,你是可以任意取的,如下一个计数的counter纯函数

function counter(state = 0, action) {
  switch (action.type) {
  case 'INCREMENT':
    return state + 1;
  case 'DECREMENT':
    return state - 1;
  default:
    return state;
  }
}

从上面的例子看得出,reducer函数不光接受action为参数,还接受state参数,也就是说,Redux中的reduce函数只负责计算组件的状态,却不负责存储组件的状态

在Reducer函数中往往包含action.type为判断条件的if-else或者switch语句,根据action,总是返回一个新的状态,这个新的状态的结果返回给store,store就会将原来上一次的state进行替换更新,最终达到改变state这么一个过程

结语

本节主要介绍了Redux,它与React是两个独立的产品,两个框架做的事情的方向不一样,React是用作于视图层的渲染,也相当于MVC中的V层,而Redux它是用于管理组件公共数据的Model层,更近一步讲,它是Reducer与Flux的一种结合,改进.

对比了使用Redux与不使用Redux的区别,以及Redux的工作流,最后Redux的设计基本原则,其中前两个,个人觉得对于理解Redux是非常重要的

当然现在也可以使用高阶组件,React hooks的写法,可以不用Redux了的,也有类似于dva这样的框架,基于Redux以及中间件(Redux-saga)的数据流方案

但是Redux依然是主流,只要你能够应付项目中开发需求,哪个用得爽就用哪个的,Redux虽然确实是绕了一些,有时候在各个文件之间进行来回切换,对于模块化的拆分,如果不是很清楚Redux的使用流程,无论是后续维护还是迭代升级,都挺痛苦的

本篇并不是什么高大上的内容,比较浅显,概念性的文字比较多,后续会结合具体的代码进一步理解Redux

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

推荐阅读更多精彩内容