【D1N910】一线大厂React实践宝典 React中的数据管理(进度 5/9)
正常操作,正常分析,大家好,我是D1N910。
在大约两年前,我购买了 腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。
因为我一直基本上是使用Vue来进行前端页面的开发,但是一直没有时间去实践看完。 两年没去看了,真的很惭愧,时间,唉,过得真快啊。
为了在这门课过期之前看完,所以我要抓紧时间学完了。
本系列的专栏记录了我学习 一线大厂React实践宝典 的笔记。
下面的内容版权归属是 腾讯课堂【NEXT】一线大厂 React 实践宝典 这门课。
当然,如果我微不足道的笔记也算是我自己的版权,那么也是有我的小小一份。
你可以用来学习 React。
但是如果你用来商业化盈利,请先获得 腾讯课堂【NEXT】一线大厂 React 实践宝典 制作方的许可。
BTW,作为挖坑狂魔,如果这篇专栏没有更新,那么当我没说过。
这是一个系列的文章,如果你没有看过前面的内容,那么建议你先看一看



这是一个自己的约定,五一,冲冲冲!
现在,拿下 《 React中的数据管理》这座山,能达到今天进度的 2/3,宝贝,加油!
而且很重要的是,如果成功翻越了这座大山,那么对于我自己来说接下来都是船新版本!从之前到现在的 React中的数据管理 ,是我两年前已经学习过的内容!后面就没学习了,所以,要坚持下去!
五一还有一天又一个小时,冲冲冲!

组件间的简单通信
玩转 React 单向数据流(小剧场)
A:最近 React 学习得如何了
B:最近我正在用父组件包裹子组件,对于子组件之间的共享数据,我正在研究应该如何处理呢。
C:其实你可以把所有的数据都放在父组件中进行存储,然后再由父组件传递给子组件,这样也不会违反单向数据流的原则。
B:但这样有一个问题啊。子组件该如何重新接受数据,并重新渲染呢?
A:如果你采用 props 传输数据,子组件是会自动接收到更新并渲染的,无需手动处理。
数据流
这一节,将从数据流的角度研究 React。
数据流表示了系统中数据的传递方向
React 的特性之一就是单向数据流
数据只能从上层传入,或者由自己维护
以一个 Component 为例子,它可以从父组件中拿到 props,这样从父组件从上到下拿到数据,就是 React 中的数据流。
Component 本身也可以维护 state,组件内部定义、使用和改变 State。
在构造函数中初始化我们的 state,用 this.state 调用,用 this.setState 进行修改,这些都是在组件内部完成的。
那么组件与组件是如何传递数据的呢?
组件之间彼此相互独立,实现特定的功能。想要能够互相传递数据,那么就要有关联关系,比如父子组件的关系。
组件间传递数据的四种类型
1、父组件向子组件传递数据(单独使用 props 即可实现)
2、子组件向父组件传递数据
3、兄弟组件之间传递数据
4、任意两个组件之间传递数据

状态提升
上一节说了组件之间传递数据的几种情况,也明白父组件向子组件传递数据的方法是通过 props,这个是我们已经接触过的。
剩下的三种类型将在这一节进行论述。
子组件向父组件传递数据
父组件可以通过 props 传递任何数据给子组件,包括函数。
当我们把函数传递到子组件的时候,我们的子组件是可以调用这个函数的,通过调用这个函数,可以让函数把数据传入到父组件的函数体内。
父组件 -> 传递函数 -> 子组件 -> 调用函数(带参)-> 父组件
将 props 设置为回调函数可以解决子组件向父组件传递数据的问题。
将函数通过 props 传递给子组件
这边举了一个简单的例子,在子组件挂载的时候调用父组件的方法。

当然,如果想要调用的时候执行父组件的更新状态,则需要在父组件里绑定 this 。

兄弟组件传递数据
兄弟组件传递数据的方式是:通过父组件连接子组件进行数据交换
兄弟A -> 数据 -> 父组件 -> 数据 -> 兄弟B
举个例子,下面的两个兄弟组件分别可以影响它们的兄弟数据。

我们这里没有单独在子组件的 state 中维护我们的 count 变量,而是放到了父组件中,这也就相当于数据被“提升”到父组件中。这个就叫状态提升。
任意两个组件传递数据
通过全局变量连接各个组件
发布-订阅模式:实现一个消息组件,每次需要传递数据的时候,会抛出一个事件,对这个事件有兴趣的组件会监听这个消息,监听到这个消息会进行数据变换。
window下的全局变量:这种一般是用来读取配置。
Context:这种是通过 React 的 Context,通过访问上下文,使得能够访问得到应用的共同区域。这种也一般是用来读取全局配置,比如应用的主题色,各个组件都需要访问,但是不会去进行配置的一些东西。
* 这部分的内容,按照课程安排,放在下下节的资料篇进行讲解
本小节完毕,相关代码如下
https://github.com/D1N910/learn-react/tree/master/Chapter4/demo-1-send-data

简易聊天室
这个算是要自己完成的项目了,就是利用上面讲的状态提升的概念,完成一个聊天室。
效果如下,发送的时候,三个聊天框能出现刚刚发送的内容。

能够实现就好,当然,你也可以看看我的做法,代码如下
https://github.com/D1N910/learn-react/tree/master/Chapter4/demo-2-chatroom
我的代码里没有添加类型检查,你加了就最好了

【资料】发布-订阅模式(大量参考了原资料)
刚刚我们有聊过,不同组件之间可以通过发布-订阅模式来实现数据更新。
首先介绍下 发布-订阅 概念。
什么是发布-订阅模式?
发布-订阅模式一般由下面三个部分组成
发布者:发布消息,供订阅者订阅(不知道是谁订阅了,他也一般没必要知道)
订阅者:订阅特定类型的消息(不知道是谁发布的消息,他也一般没必要知道)
功能器:作为中介连接发布者和订阅者,发布者可以向功能器发布消息以供订阅者订阅,订阅者可以向功能器订阅特性类型的消息。
生活中,有一种类似发布-订阅模式的例子就是广播电台。
不同的广播站发布消息 -> 广播台处理转发出去 -> 用户收听自己爱听某个广播站
发布-订阅模式优点
松耦合
发布者不需要知道订阅者的数量,或者订阅者是通过什么方式运行的。他们能够相互独立地运行,这样就可以让你分开开发这两部分而不需要担心对状态或实现的任何细微的影响。
更干净的设计
充分地利用好发布-订阅模式,你不得不深入地思考不同的组件是如何交互的。这通常会让我们的设计更加干净整洁。
灵活性
你不需要担心不同的组件是如何组合在一起的。只要他们采用同一种发布-订阅的实现。
容易测试
你可以很好地找出发布者或订阅者是否会得到错误的信息。
当然,发布者和订阅者隔离也会导致系统不稳定、发布者或订阅者数量激增时出现问题等缺点,但是一般而言,项目中恰当地使用发布-订阅模式是有助于我们的实际开发的。
发布-订阅模式实现
下面是原生代码实现应用的 发布-订阅 模式
class Events {
constructor() {
// 缓存 被订阅的事件以及对应的回调函数
this.cache = {}
}
/**
* 发布
* topic 发布的事件名称
* parameter 传递的参数
* scope 绑定执行的对象
**/
publish(topic, parameter, scope) {
const thisTopic = this.cache[topic]
// 如果有订阅
if (thisTopic) {
let i = thisTopic.length - 1
for (i; i >=0; i -= 1) {
// 执行订阅回调
thisTopic[i].apply(scope || this, parameter || [])
}
}
}
/**
* 订阅
* topic 发布的事件名称
* callback 回调参数
**/
subscribe(topic, callback) {
if (!this.cache[topic]) {
this.cache[topic] = []
}
// 添加回调
this.cache[topic].push(callback)
// 可以方便用于取消订阅
return [topic, callback]
}
/**
* 取消订阅
* handle [topic, callback]
* complety 是否把所有该同名事件订阅都取消
**/
unsubscribe(handle, complety) {
const topic = handle[0];
const callback = handle[1];
const thisTopic = this.cache[topic]
if (thisTopic) {
if (complety) {
delete this.cache[topic]
return false
}
let i = thisTopic.length - 1
for (i; i >= 0; i -= 1) {
if (thisTopic[i] === callback) {
this.cache[topic].splice(i)
}
}
}
}
}
// 测试
// 广播台
const event = new Events()
// 订阅一个天气
const theWeather = event.subscribe('weatherChange', (e) => {
console.log('现在的天气是', e)
})
// 发布一个阴天
const weather0504 = event.publish('weatherChange', ['阴天'])
// 发布一个晴天
const weather0505 = event.publish('weatherChange', ['晴天'])
// 取消订阅
event.unsubscribe(theWeather)
// 发布一个下雨天
const weather0506 = event.publish('weatherChange', ['下雨天'])

你也可以直接用下面的方式获得代码 (记得把注释打开)
https://github.com/D1N910/learn-react/blob/master/Chapter4/demo-3-publish-subscribe/index.html
在刚刚的聊天室中使用 订阅-发布模式
1、添加一个工具包文件夹,里面放置各种工具,比如这个 订阅-发布 工具

2、在 index.js 中使用 订阅-发布 工具,并且在公共对象上创建一个广播台实例

3、整理干掉父组件刚刚绑定的修改聊天室记录的方法

4、修改 talker
在 componentDidMount 生命周期里,订阅 聊天记录更新 的消息

原来的提交,改为发布一个消息

最后效果发现保持一致

(1)原来文章写的取消订阅方法有问题,我上边的整理优化了

(2)聊天室的 消息-订阅 模式 原创整理完成,所以我也知道为啥 componentDidMount 这个方法推荐的除了做 ajax 请求,还可以做消息订阅是怎么回事了。
聊天室改造为 消息-订阅 模式的代码在下面
https://github.com/D1N910/learn-react/tree/master/Chapter4/demo-3-publish-subscribe

【资料】Cntext(大量参考了原资料)
什么是 Context?
在一般的 React 程序中,数据通过 props 自上而下由父对象传递给子对象,但是对于某些变量来说(比如全局环境变量、全局主题等)如果层层传递,会增加很多麻烦。而 Context 提供了一种在组件之间共享这些值的方法,无需通过组件层层传递 props。
在 Context 的使用中,有两种角色,一种是 Context 的生产者(Provider),通常是一个父节点,另外一种是 Context 的消费者(Consumer),通常是一个或多个子节点,因此,我们也可以将 Context 理解为一种生产者-消费者模式。
这里用的 Context 主要基于 V16.3+
Context 的使用 —— 以用 Context 来将 theme 到孙子组件为例子。
下面用 context 实现按钮主题色变化。

代码如下
https://github.com/D1N910/learn-react/tree/master/Chapter4/demo-4-context
这边其实不建议用 Context,这属于高级的 API,而且感觉改动得有点快。
使用 Context 传递的内容过多,反而会让我们的数据流向不清晰,从而让我们的应用变得难以维护和调试。

Flux
它来了,它来了,它作为一个新的知识走来了 —— 虽然上面的,对于我来说,也是新的知识。
Flux介绍(小剧场)
A:我看你在这里愁眉苦脸半天了,是在解决什么复杂的问题吗?
B:对啊,本周我收到一个任务,是要给一个社区页面增加一些新的功能。但是涉及到很多组件的联动。一些组件的更改,往往需要其他组件也会进行反应,这个问题做起来比较复杂。
A:那你现在是怎么做的呢?
B:我现在采用的是父子组件的通信以及子组件事件通知的方式。但是管理起来,还是很复杂。
C:听你们讲述的内容,感觉你们可以用一下 Flux 架构。
A:通过使用 Flux 架构,我们可以把共有的数据管理起来,我们可以比较方便地实现你这里的这些功能,而且我们的代码逻辑也能更加清晰。
B:那我就需要很好了解 Flux 了。
Flux 设计理念
通过上面的学习,我们以及可以在各个组件之间传递数据了,但是大量的 props 、全局变量的情况下,会让我们的项目难以维护。
Flux 是一种使得 React 应用数据更加严谨地单向流动的架构。
以往的数据架构是什么?
有名的 MVC 架构
Action -> Controller -> Modal -> View
看起来清晰,但是在我们的数据模型复杂后,就会发现变得非常繁杂了,交互会变得很凌乱,因为没有一个单向数据流。项目很大以后,会显得很难维护。
比如某个 Model 的更改,并不知道会影响哪个 View。

同样的,如果 React 当中的数据流,如果不加以控制,也同样会复杂不清晰,难以维护

如何有效管理数据流的变化?
解决方案:两者结合,更加受限的单向数据流。
提供一个 control center,数据中心,所有的数据会上传到数据中心处理,然后再从数据中心获取值。
这样的数据交互方式就很清晰了。

只需要和数据中心做交互,而不需要和其他组件做交互。
数据总是从源节点流向控制中心,再流向目标节点。
流程如下

通过唯一的 Dispatcher 来去控制传递事件。
React 作为视图层 View,Flux 中的 Store 则作为数据中心 M, Flux 中的 Dispatcer 则可作为一个控制中心 C,负责用户行为的响应和 Modal 层的改变,。
React 不一定需要绑定 Flux,但在复杂度高的项目中使用 Flux 架构,可以让项目中的数据流更加清晰可预测,利于项目的维护和迭代。可以说项目复杂度越高,越适合引入 Flux 架构,。
Flux 约定我们视图层不直接修改组件的状态,应用的状态应当放到 Store 中进行管理,组件状态的修改需要触发 Action 。
这上面就是 Flux 的主要流程。(感觉有点像是基于上面学的 发布-订阅 模式)
使用 Flux 管理数据
Flux 是一个架构而不是框架,业界有很多的实践,比如 React-Flux,Redux(下一节的内容)。其中的 View 层可以有很多实现,比如这里,我们就是在 React 上实现。
生产开发的时候,不一定就要选用这种架构进行开发,这里只是作为教学的介绍。
下面将通过实现一个 TodoList 项目来实践 Flux。
在我们的普通文件下, 新增了 action.js、dispatcher.js、store.js,分别就对应上了刚刚流程里的部分内容。

Actions.js

发生的事件
语义化
准确描述的对象
首先定义一个 ActionsTypes 对象
定义所有 Action 类型
一般我们用大写字母表示是一个敞亮
这里类似于枚举值
尽管它的值可以为任何类型,但是为了便于区分和比较,我们一般用字符串

其次定义各种行为方法。
可以使用 Dispacher.dispatch() 方法去派发 action,虽然没有明确的定义,但一般约定。
type:用以区分类型,值必须是上文中定义的 ActionTypes 之一。
payload: 触发这个行为的时候携带的要传递参数。
上面这两个变量名称,具体有没有这两个变量,都是我们自定义的,而不是需要特别约束的。
上面的编码只是一种老师认为好的开发风格。
后面的 store.js 就可以证明这一点。
整个派发方法类似这样

* 感觉很像 发布-订阅 模式的发布方法。
Dispatcher.js

控制中心
保持单例(整个项目中,只有唯一的 dispathcer)
接收 action 派发给 store
这个文件就简单了,就是从 flux 组件中导入了 Dispatcher,然后导出了这个 Dispatcher 创建的实例。

* 感觉很像像 发布-订阅 模式的实例化广播台。
Store.js

存储应用中的数据
用于接受 actions
只可被 action 修改
允许多个实例(下面的例子我们只用了一个)
首先引入 ReduceStore 。ReduceStore 是 Store 的一种实现,这里借鉴了 Redux 的纯函数的方式,我们之后会涉及。然后需要在构造函数中绑定 Dispatcher,这里的目的是为了能够监听自动执行后面的方法。

getInitialState() 在这里我们相当于在 Store 定义了 state,这里会填写我们要的 state 以及对应的初始值。

reduce(state, acton) {}
@return {*} 返回下一个状态
@param {*} state 上一个状态
@param {*} action 传入的action
ReduceStore 中一定要有一个 reduce 方法
它应该是一个纯函数
Dispatcher 派发的所有 action 都会到这里来
我们根据 action 的类型来改变我们的 state
比如下面的 reduce,我们就用 switch 实现了分别对 添加方法 以及 删除方法 的处理

这里其实相当于就是广播站站长很闲,专门针对触发的事件做个归类,然后在广播站针对这些事件做一些处理。
最后也是导出一个 Store 实例

只要这里的 state 发生了变化,那么就会通知到 view 里面去
View

展示 stores 中的数据形态
可以是任何的前端框架(这里是 React)
和 Store 共享数据
下面介绍在 React 中如何监听到 state 变化并更新。
首先,通过 getInitialState 方法获得初始值

在组件挂载后 componentDidMount 绑定监听函数 addListener ,这个监听函数传参是 state 变化后会执行的方法。addListener 会返回一个函数,这个函数在调用后会移除监听。
所以我们也会在 componentWillUnmount 里移除监听。
这里就很像是 发布-订阅 机制里的订阅机制了。

每次 Store 更新之后我们都需要重设组件的 state,用 getState 方法获得最新的 state。

触发指令事件的方法
刚刚我们引入了 Action
import Action from './action'
那么只需要调用对应的方法即可。

最后尝试执行~

上面的例子,能够总结 flux 的特点。
中心化控制,改变通过 action 发出,由 dispatcher 分配
view 保持简介,只需要关心传进的数据
数据始终保持单向流动
本小节完毕,代码如下
https://github.com/D1N910/learn-react/tree/master/Chapter4/demo-5-flux

本节最开心的还是学到了 发布-订阅者 模式!
上面的内容都是学习自 腾讯课堂 【NEXT】学院 一线大厂React实践宝典
个人学习笔记
本期的课后作业抄录
给博客配置并且增加主题可选项
我们可以在博客的底部增加一些可点击项目,从而让我们的博客可以展示不同的主题:
增加 Show Tags | Hide Tags 可选项,从而可以配置是否在首页的博客列表展示博客的标签,在不展示标签的情况下,相同范围内可以显示更多的博客条目。
增加 Pagination | No Pagination 可选项,从而可以配置博客首页文章列表是否分页展示,由于我们的文章列表之前采用的是前端分页,因此这方面的实现较为简单。
在完成以上几个可选项之后,大家可以按照相同的方式添加更多的可选项,比如配置颜色、背景底纹、深浅主题等。
将博客主题可选项配置持久化
假设用户选择了我们提供的可选配置的能力,但是如果页面一刷新又恢复如初,这样的用户体验显然不佳,所以我们需要做的就是“记住”用户选择的配置,持久化存储在浏览器里面,这样当用户下次来访问的时候,可以预先读取用户之前选择的主题项目配置,并且依此展示。
这方面的实现,我们可以采用浏览器提供的 Window.localStorage。
叹气
五一劳动节,五一、五二发烂渣 + 玩游戏
五三、五四、五五才开始继续学习
所以现在还没学完~
还剩下 4 个项目
不过~感觉收获颇多!
剩下的,希望这周能够完成吧 ✅
加油,加油,加油
前路肯定苦难重重,无边无际的地狱
熬夜的痛苦,肝的魔咒
头发会没有
幸福会没有
但是还是喜欢这样
这大概就是~
做好了喜欢你的准备吧
感谢也很努力的你,喜欢你!
感谢 曾轶可 的 《狮子座》陪伴我写完这最后一点点内容
学习进度( 5 / 9 )
from 蛋糕