Redux 学习纪要

背景

Redux 是 React 全家桶的重要一员,理解 Redux 可以更好的帮助我们开发前端项目。本文只谈论概念的解读,不讲解具体的用法。

Redux 和 React-Redux

首先声明 Redux 和 React-Redux 的区别。 Redux 这个项目严格来说,并不局限于 React,它可以用于 ES6, Angular ,React。而 React-Redux 是 React 官方提供的一个集成 Redux 的包。

为什么要 Redux

这个要从 React 的哲学讲起,在 React 中,有两个重要的概念 props 和 state,
props 一般是外部组件传入的,而 state 是组件内部的状态。也就是说数据从外部更大的组件传递到内部被包含的数据。

React 中的数据流向

这样的方式,非常直观,对于一些平铺式的页面非常有优势,比如资讯类应用,资讯只需要瀑布流式的展示,资讯之间没有交互。

可是并不是所有的页面都那么简单,也有很多场景,我们页面组件之间还是需要交互的,这个时候就不一样了。组件之间的关系是平级的,没法直接传递数据的。

组件无法直接传递数据

没有 Redux 的情况

针对非包含关系的组件间传递数据的问题,React 官方文档提到了一种解决办法,见 Lifting State Up, 其思路其实是添加一个组件,我们暂时称它为外包组件,让它包含需要交互的两个组件,然后通过被包含的组件触发 callback 方法,引起外包组件变化,然后外包组件再修改另一个组件。

callback 方式传递数据

这当然是解决问题的一种方式,但是这种方式却会带来一个问题,因为一旦需要交互的组件最近公共外包组件特别远的话,需要大量地修改代码,一层一层的传递 callback 函数。这样的工作量简直不要太大。

当组件支架的最近公共外包组件很远的时候,callback简直崩溃

Redux 是怎么做的

在写 React 的时候,通常我看到一个前端组件的时候,我们总是很自然的认为组件应该包含 view 和 state 两个部分。组件应该高内聚的维护其 view 和 state。 但是 Redux 不这么思考, Redux 认为组件最好只维护 view , 而 state 应该在一个地方集中的维护。

redex 数据集中化管理图示

Redux 将所有的数据,集中在一个地方,组件可以直接引用这个数据源,同样也有了修改这个公共数据源的能力。通过这样的方式解决了组件间数据流动的问题。

Redux 中重要的概念

知道了 Redux 做了什么,现在我们来看看 Redux 为了实现这些功能,都提供了哪些概念。

Store

store 就是存储全局数据的地方。组件可以通过连接到 store 来获取到全局的数据。

Action

当组件可以通过连接 store 来查看和修改全局数据,用于触发变更。但是比起直接修改 store,更好的方法是使用命令模式,组件只需要发布命令就好了,然后让具体的执行者去执行,这样就可以将命令和执行解耦开。 Redux 使用了这样的设计模式,组件发送命令,然后由 Reducers 去执行具体的命令。而 Redux 的 action 正是命令模式中的 command 。action 中包含命令的类型 type 和命令的内容。

Reducers

Reducers 就是命令模式中的命令接受者,Reducers 接受到 action,然后根据 action 的内容去对 store 执行相应的变更操作。

pure function (纯函数)

Reducers 的具体实现其实就是一个大的函数,Redux 官方文档声明成为 Reducers 的函数必须是一个 pure funciton。所谓纯函数和我们常说的无副作用函数是有区别的。

纯函数: 在任何时刻,对于给定的一个入参,只有唯一一个对应的结果

根据上述的定义,类似 f(x) = x + 1。 这样简单的函数就是纯函数,纯函数最主要的概念是将时间区分出来了,所以类似向服务端发起请求获取文章 pv 的接口就不是纯函数,因为 pv 会随着时间的推移而发生改变。之所以要求是纯函数,据 Redux 作者在自己的博客 The Evolution of Flux Frameworks 中所说的

Stores and actions are just pure functions. They are easily testable in isolation.

主要是为了易于测试,因为纯函数便于做重放操作。

dispatch

顾名思义,就是分发器方法,负责将 action 分发到 Reducers 。

subsribe

订阅方法,参数是监听器,监听 store 的变更事件,当 store 发生变化的时候,触发回调函数

connect

这个方法功能主要的是将当前组件和全局的 store 连接,并将 dispatch 注入到当前的组件的 props 中。 当 store 更新的时候,会通过 connect 传入的回调函数将更新后的数据通知到当前组件。而且由于 connect 可以方便的指定当前组件需要关心的数据,所以用了 connect 后, subsribe 基本上就没有使用必要了。

Redux 数据流程

Redux 数据流程

Store 与 render 的触发

当 store 发生变化的时候,会通过 connect 中的回调函数触发组件重新 render,以此来改变页面内容。

Store 的局部改变

当 store 发生改变的时候,改变的数据可能只是局部的一小部分,当小部分数据发生变更的时候,redux 会只更新局部的 view ,而其它不发生改变,这样有助于提升性能。

如何判断数据是否更新

数据要更新了才能触发 render,但是如何判断数据已经发生改变了呢。众所周知,对于嵌套层次特别深的数据,很难判断数据是否 update,而且如果要判断,必须递归地判断,这样性能是比较差的,所以 react-redux 使用了浅层判断的方式,类比 C++ 和 Java 其实就是判断对象的内存地址,在 JavaScript 中其实就是 == 判断。

嵌套判断

因为使用了浅层判断,所以对于一个数据,比如 a.b.c.d ,如果我们要更新 d 数据,如果我们只更新了 d,是不够的,因为 a 没有更新,无法通过浅层判断。但是仅仅更新 a,d 也不够,因为到了 b 依旧无法通过浅层判断,所以如果要更新d ,则 d,c,b,a 都要更新,也即更新一个数据,其所有的父对象也都要更新。
这个要特别关注,因为经常出现的页面没有重新 render 的原因,大多是因为没有重新生成父对象。

Redux 和传统的 发布/订阅 系统

虽然 Redux 中也有发布和订阅,但和我们通常使用的基于观察者模式的 发布/订阅系统却有所不同。

Redux 添加组件不符合开闭原则

我们常用的发布/订阅模型,订阅者向消息中心订阅消息,然后收到消息,在这个过程中,不需要修改现有代码,而在 Redux 中添加组件,不仅需要修改初始化 store,还需要修改 redurces ,用于配合修改新添加的组件的 state。在这个过程中存在代码修改,不符合开闭原则。

没有消息堆积功能

Redux 中没有原生的消息堆积的概念。

组件不符合内聚性

使用了 Redux 的组件,因为其和全部变量绑定,也就意味着和整个应用紧耦合了,这是不符合高内聚原则的,也即意味着使用了 Redux 的组件跨应用的复用性差,所以 Redux 更适合开发有复杂交互的 webApp,而不适合用于开发需要高可复用性的组件。如果自己开发的组件也有一定的交互,可以考虑自行开发一套简易的发布/订阅工具。我在我自己的项目中,开发的组件,为了能不用 redux 实现消息的订阅和发布,自行开发了如下简易的发布/订阅工具

const dispatchMap = [];

export function registerListener(topic,callback,tags = '*'){
    let listener = {
        topic: topic,
        tags: tags,
        callback: callback
    };
    dispatchMap.push(listener);
}

export function publish(msg){
    dispatchMap.forEach( (listener) => {
        if(listener.topic == msg.topic){
            if(listener.tags === '*' || listener.tags.includes(msg.tag)){
                listener.callback(msg.content);
            }
        }
    });
}

总结

redux 之所以能在 react 中流行,归根结底是因为它能解决 react 中数据流动性差的问题,搞明白这点之后,很多 redux 的设计也就能理解了。所以说学习一个新的东西,我认为最重要的部分,是先学习我们为什么需要它,当我们搞明白为什么需要它之后,然后再学习它是怎么解决的,就会少很多疑惑,不会一来看到一大把术语而不知所措。

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

推荐阅读更多精彩内容