You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
小彭一听,“ 哎不对啊,不是可以通过回调进行修改父组件的 state 吗?” 是的,确实可以。我们来模拟一个场景,如果你想两个兄弟组件之间进行交流,互相八卦,交换数据,你咋整?
这个图应该都看得懂哈,也就是说,我们兄弟组件想互相交流,交换对方的数据,那么唯一的解决方案就是:提升 state,将原本 Peng、Kuan 组件的 state 提升到共有的父组件中管理,然后由父组件向下传递数据。子组件进行处理,然后回调函数回传修改 state,这样的 state 一定程度上是响应式的。
朴素的状态管理
上边所说的方式,就是 React 中最朴素的状态管理方式,但是这种方式会带来什么问题?
遇到需共享状态的组件,你得把需要共享组件的 state 集中放在所有组件的顶层(state 提升),然后分发下去
Redux 官网中,有这么一句话 : Following in the steps of Flux, CQRS, and Event Sourcing, Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen. 出于好奇,我去把这东西大概了解了一下,如果有误,望大佬们指出 🤝
Flux 允许存在多个 store,Redux 只存在一个,相对于 Flux,一个 store 更加容易管理,较为清晰。
Flux 中多 store 存储状态,并且在 store 里执行更新逻辑,当 store 变化时,通知 Controller-view 更新自己的数据;Redux 是将各个 store 整合成一个完整的 store,遵循 Event Sourcing 规则,通过这个 store 计算推导得出完整的 state
Redux 的更新逻辑在 reducer 中,而不是在 store 中。单一 store 的好处是,所有数据结果集中化管理。只需要传给外层组件,那么内层不需要维护 state,全部经过父级由 props 向下传即可。
本文流水线
看完这篇文章你能学到什么
Event Sourcing
、CQRS
、Flux
相关知识正文开始
React 单向数据流特性
想必很多人都知道,React 是单向数据流,那么在 React 中,想要进行数据的传递,该怎么做呢?下边这张图告诉你~
在 react 中,有 props 和 state,当我们想从父组件给子组件传递数据的时候,可通过 props 进行数据传递,如果我们想在组件内部自行管理状态,那可以选择使用 state。但是呢,我们忽略了 react 的自身感受~
react 它是单向数据流的形式,它不存在数据向上回溯的技能,你要么就是向下分发,要么就是自己内部管理。
小彭一听,“ 哎不对啊,不是可以通过回调进行修改父组件的 state 吗?” 是的,确实可以。我们来模拟一个场景,如果你想两个兄弟组件之间进行交流,互相八卦,交换数据,你咋整?
这个图应该都看得懂哈,也就是说,我们兄弟组件想互相交流,交换对方的数据,那么唯一的解决方案就是:提升 state,将原本 Peng、Kuan 组件的 state 提升到共有的父组件中管理,然后由父组件向下传递数据。子组件进行处理,然后回调函数回传修改 state,这样的 state 一定程度上是响应式的。
朴素的状态管理
上边所说的方式,就是 React 中最朴素的状态管理方式,但是这种方式会带来什么问题?
如果说,我们项目足够简单,这种方案其实是没什么问题的,如果引入了额外的状态管理方案(redux、hox、mobx...),反而会加重每个组件的负担,造成多余的依赖。
我举个例子,以我们项目组当前的项目为例,存在以下特点 :
这就导致于,我们无法避免各组件之间的状态共享;按照朴素的状态管理就有些捉襟见肘,相对鸡肋了。
为此,需要一个库,来作为更加牛逼、专业的顶层 state 发给各组件,于是我们引入了 redux,让我们的状态更加可控,让一切都有据可循。
Redux 解惑
下边主要是对初次尝试使用 redux 的小伙伴,进行一个解惑,如果有理解错误的,望大家指出 🤝
redux 整个应用的 state 都存储在一颗 state tree 中,并且只存在于唯一一个 store 中~ 那么会有小伙伴好奇了,what ? 那我为什么会看到好多个 reducer ???
redux 提供了一个
combineReducers API
,怎么说呢?小彭项目初次搭建的时候,要求小,状态管理比较方便,所以呢,都放在了一个 reducer 中,后边随着不断迭代,于是不断的往这个 reducer 中塞数据。典型的屁股决定脑袋,于是有一天,可能某个天使,给 redux 的开发团队提了一个 issue, “哎呀,你能不能提供一个 API,把我的所有 reducer 都整合在一块啊,我想分模块化的管理状态”
于是 redux 提供了 combineReducers 这个 API,看来 redux 的时间管理学学的很好,你看,这么多个 reducer ,都能整合在一起,想必花了很大的功夫~ (如果你想知道 combineReducers 原理,👉 看这里
又有小伙伴问了,我们用了 redux,那么它是如何跟我的组件勾搭上的?这时候你就需要知道
connect
了,这是 react-redux 提供的一个 API,想知道原理的,可以自行去查询一下原理哈~你还会看到这么一段代码
你忍不住问到,这个 Provider 是个啥?其实这是 React 中的“提供者模式”。我前边说过了啊,在 React 中,props 是组件之间通讯的主要手段,那么如果你隔着好几层其他组件,进行通信,这种是很不合理的。
所以呢, React 官方推荐了一种 Provider 模式。这个模式有两个狠角色,一个叫“提供者”,另一个叫“消费者”,这两个角色都是 React 组件。其中“提供者”在组件树上居于比较靠上的位置,“消费者”处于靠下的位置。
“提供者”可以提供一些信息,而且这些信息在它之下的所有组件,无论隔了多少层,都可以直接访问到,而不需要通过 props 层层传递。
所以你能理解我们的
根App
为什么要加Provider
了吧,不加的话,你即使在根 App 上挂载了 store, 你仍然还是要一层一层的传递this.props
,否则最里层的拿不到 redux 中的值。你甚至还可能会看到,redux 中使用了
Immutable
,小朋友,你是否有很多问号 ? 为什么,别人在那写代码,我却在学卧槽,对着代码说 giao , 阿宽告诉你要做个乖宝宝让我们想一想,为什么要用 Immutable,它是个啥玩意?上篇文章说了,在函数式编程语言中,数据是不可变的,所有的数据一旦产生,就不能改变其中的值,如果要改变,那就只能生成一个新的数据。
所以初步情况下,我们会看到
reducer
的代码是这么写的 :那么你就会问了,为什么数据不可变,为什么需要返回一个新的数据啊?这么做会有什么问题啊?
问题可大了,react 中有个 生命周期,叫做 shouldComponentUpdate ,它会帮你拦截组件渲染,怎么理解?我们知道,react 在上一轮(prevProps/prevState) 和下一轮(nextProps/nextState) 的对比中,是进行浅比较(为啥不深比较?你猜)
那么对于一个对象来说,它对比的是这个对象的引用地址,所以你修改了此对象的值,实际上只是 value 改变,但该对象的引用地址未发生变化(懂得自然懂.jpg)既然未改变,那么就不会 re-render 咯 ~~
所以我们需要返回的是一个新对象,而不是原对象,到这里你知道为什么 redux 总要返回一个新的 state 了吧?
又有人问了,为什么不直接
Object.assign
呢?我也很好奇过,直接拷贝一份,然后返回不就好了嘛,为什么要用Immutable
库呢?在我查阅资料之后,嗯,可以,说服了我,感兴趣的可以去看看这篇文章 : 精读 Immutable 结构共享,看完你就明白了。Redux Motivation
Redux 官网中,有这么一句话 : Following in the steps of Flux, CQRS, and Event Sourcing, Redux attempts to make state mutations predictable by imposing certain restrictions on how and when updates can happen. 出于好奇,我去把这东西大概了解了一下,如果有误,望大佬们指出 🤝
什么是 Event Sourcing ?
举个 🌰,你们平时有记账习惯吗?我有,我会在备忘录中记录每个月的开销
一般来说,我们记账都有两种方式,一种是 : 直接记录每次账单的结果。另一种是,记录支出/收入。我们可以自己计算得到结果,Event Sourcing 就是这种。
在这个图中,左边是我们的账号对象,他有几个事件处理函数,这里展示 2 个,一个是 AddAccount(记录收入),一个是 SubAccount (记录支出)
左边是一个个的事件,它是一个事件流,记录我们的一些支出/收入的事件。当这些事件产生的时候,就会出发 Account 对象里相应的处理函数。
右边是处理完之后,账户里边金钱最新的状态数据。
上边这些事件需要持久存储于数据库或者其他地方,而 account 数据不需要,我们只需要在每次获取 account 当前的数据状态时,通过调用 account 的相关事件,重新计算生成。
Event Sourcing 和 传统的 CURD 区别在于:
所以我认为,在 Redux 中,体现 Event Sourcing 思想是:Redux 中的每一个 state 都是独立的、连续的但无关联的,我们可以通过每一个 state,推导出完整的 state。
什么是 CQRS ?
CQRS(Command Query Responsibility Segregation) ,顾名思义,“ 命令查询职责分离 ”,也就是 “读写分离” ,一个方法要么作为一个“命令”去执行一个操作,要么作为一次“查询”向调用方返回数据,但两者不能共存。
我们不禁想问 : 为什么要做这样的分离?原因如下 :
数据的读和数据的写,次数是不平衡的,把读、写分离,能有针对性的优化它们怎么理解呢?你们买过机票、火车票,预定过酒店吧 ? 你会第一眼看中就下单吗?或者这么说,你读文章的次数比你写文章的次数多吧?不会吧?不会到现在还有人没看过阿宽的文章吧?不会吧不会吧~
古人云 : “距离产生美~”,一般读操作,都是比较简单的,写操作就比较复杂麻烦了。所以分离对我们来讲,能更好维护
CQRS 里边的一些概念:
实际上,Event Sourcing 和 CQRS 有着一定的联系,在 CQRS 使用了 Event Sourcing 模式以后,会产生额外的好处。
对于 Command 端,它会通过 Event Sourcing 更新聚合对象的流程,这是会有一个 Event Handler 的处理类监听相应事件,更新物化视图(在某个事件发生时,将聚合对象的最新数据状态存入到一个表中,这个表就叫做物化视图)
对于 Query 端,只是对数据库的读操作。但注意 : 用户进行查询得到的数据可能不是最新的。会有几个毫秒的延迟。
所以要是使用了 CQRS 架构的一个前提 : 你的系统能够接受系统使用者,查询到的数据可能不是新的。因为一个多用户同时访问,在高并发修改数据的情况下,比如秒杀、12306 购票,用户 UI 上看到的数据总是旧的。
什么是 Flux ?
Flux 是 Facebook 用于构建 Web 应用程序的基本架构,它主要角色为 :
Flux 是一个单向架构,数据总是“单向流动”,任何相邻的部分都不会发生数据的“双向流动”,这保证了流程的清晰。Flux 的最大特点,就是数据的“单向流动”。它的一个流程为 :
change
事件change
事件,进行页面更新Flux 与 Redux 区别
在 Flux 中,靠近 react component 上层有一个特殊的视图层,专业术语: 视图控制层(Controller-view),它主要目的是:接受 change 事件,从 Store 中拿到最新数据,调用自身 setState 进行更新,使得 render 及后代的 re-render 触发。
而在 Redux 中,我们是将全局唯一的 Store 挂载在跟 App 上,同时通过 react-redux,使用提供者模式(Provider Pattern),调用了 React 的 Context 功能,创造一个“上下文”,在这个上下文笼罩之下,所有组件都可以访问相同的数据,从而避免 props 逐级传递。
flux 与 redux 区别:
当然,最大的区别应该为 store / reducer 的抽象, Flux 中 store 各自为战,每个 store 只对对应的 controller-view 负责,每次更新都只通知对应的 controller-view,而 redux 中的各子 reducer,是由根 rootReducer 统一管理,当我们发起一个 action 去修改某一个子 reducer,都会通过根 rooterReducer 的整合。
Redux 原理及源码解读
可以看我写的这篇文章 : 【KT】轻松搞定 Redux 源码解读与编程艺术
Redux 在项目中的问题
redux 默认只支持同步操作,让使用者自行选择异步处理方法,对于异步请求 redux 是无能为力的。可以这么说,它保证自己是纯粹的,脏活累活都丢给别人去干。
比如我们想要处理异步,所以我们在项目中引入了 redux-saga ,再比如我们想要知道日志中间件,我们引入了 redux-logger 等
举个 🌰 : 我们需要用获取户信息,就需要一整套样板代码
没错,这种样板代码,简直就是 CV 操作,对我个人而言,我觉得这会让我不够专注,分散管理 const、action、saga、reducer 一套流程,需要不断的跳跃思路。
react hooks 不香吗
基于上述过于流水线式的异步请求,那么我们是否可以进行优化呢?于是迪哥(我导师)进行了简单的封装~
主要封装了四个 hooks API
以
useSendAsync
为例子,我们并不需要关心 useSendAsync 到底做了什么东西(本质上就是返回的一个 Promise)通过此 hooks,我们的代码现在变成了这个样子
你会发现,我们只是优化了
action -> saga
这个步骤逻辑,但是数据存储到 redux ,仍然还是要 dispatch action既然已经 hooks 黑化了,为何不一条路走到底?有没有 hooks 写法,就能做到状态管理?于是在与小伙伴交流中,听到了一个新词 : hox
hox 原理
官方对 Hox 的介绍是这样的:下一代的 React 状态管理器,只存在一个 API,那就是 createModel。它的特性是
你信吗?一个 API,就能做到状态管理?giao,我去 see see,诶, 真香
在组内部提出了 hox 之后,进行了一波调研,以及落地实践,踩踩坑,看看是否可在项目中使用~
有点小尴尬的是,6.15 提出的观点,在之后对 redux、flux、hox 等进行分析,同时阅读 hox 内部源码,一直到 7.1 才进行第二轮评审,当然,最后还是给出了方案进行落地实践
其实呢,hox 就是一个 hooks,怎么理解,就跟你平常写 hooks 一样,如果你想将状态保存起来,其他组件共享,就用
createModel
包裹一下就好了~ 举个 🌰如何使用,可以看官方文档,我们接下来探讨一下,hox 的内部实现原理,感兴趣的可以去看看源码,源码很少,可以阅读一些优秀代码~~ 向大佬们看齐
内部原理
内部原理很简单:
share hooks(useModel)
,这个 share hooks 本质就是内部维护一个 state,且此 state 作为 container.data 的初始化,多次经过 createModel 包裹的 custom hooks 之所以能实现数据共享,就是因为共享的是同一个 state怎么理解呢?给你们画个图~
在我们未使用
createModel
API 的时候,我们组件 A、B 调用自定义的useSelectSubject
,会生成两份内存空间但是如果使用了
createModel
包裹之后,它就变成持久化,且能全局共享数据,原因在于它只生成一份内存空间,A 与 B 其实都是取的同一个内存空间里的数据,这样就达到了数据共享的效果。源码解读
什么,你的不是 hooks,能不能用?问题不大,hox 也支持在类组件上使用,只需要 withModel 即可,内部是通过 HOC 实现。感兴趣的可以去 github 看源码
hox 是不是真的很香 ?
前边是我简单用了一下 API 写的小 demo,感觉真的香,但在评审之后,真的抽了一个模块去落地实践了一下, 才发现,突然不怎么香了,下篇文章会记录一下 hox 的感受~~
总结
最近一直在看 React 状态管理相关的知识,前边吹了个逼,说会围绕着
hox
、mobx
、redux
进行一波交流,but,实际上就 hox 和 redux 进行对比,直播打脸了......hox 还是比较小众,目前 github 上的 star 才 518 个,所以感兴趣的小伙伴可以去了解了解,还是先积累一些经验,然后再决定是否在项目中使用吧~
相关链接
The text was updated successfully, but these errors were encountered: