资讯专栏INFORMATION COLUMN

React组件设计实践总结05 - 状态管理

ideaa / 488人阅读

摘要:要求通过要求数据变更函数使用装饰或放在函数中,目的就是让状态的变更根据可预测性单向数据流。同一份数据需要响应到多个视图,且被多个视图进行变更需要维护全局状态,并在他们变动时响应到视图数据流变得复杂,组件本身已经无法驾驭。

今天是 520,这是本系列最后一篇文章,主要涵盖 React 状态管理的相关方案。

前几篇文章在掘金首发基本石沉大海, 没什么阅读量. 可能是文章篇幅太长了?掘金值太低了? 还是错别字太多了");

所以模仿<<内核恐慌>>的口号: "想看的人看,不想看的人就别看"


系列目录

01 类型检查

02 组件的组织

03 样式的管理

04 组件的思维

05 状态管理


文章目录

状态管理

你不需要状态管理

你不需要复杂的状态管理

Redux

Mobx

RxJS

其他状态管理方案

扩展阅读




状态管理

现在的前端框架,包括 React 的一个核心思想就是数据驱动视图, 即UI = f(state). 这种开发方式的变化其实得益于 Virtual-DOM, 它使得我们不需要关心浏览器底层 DOM 的操作细节,只需关心‘状态(state)’和‘状态到 UI 的映射关系(f)’. 所以如果你是初学者,不能理解什么是‘数据驱动’, 还是不推荐继续阅读文章下面的内容

但是随着 state 的复杂化, 框架现有的组件化方式很难驾驭 f(视图的映射关系变得复杂, 难以被表达和维护); 或者相关类型的应用数据流本来就比较复杂, 组件之间的交互关系多样,本来难以使用UI = f(state)这种关系来表达; 或者应用的组件状态过于离散,需要统一的治理等等. 我们就有了状态管理的需求.

状态管理最基础的解决方式就是分层,也就是说和传统的 MV* 模式没有本质区别, 主流状态管理的主要结构基本都是这样的:

他们基本都包含这些特点:

分离视图和状态. 状态管理器擅长状态管理,所以他们一般会将应用状态聚合在一起管理,而视图退化为贫血视图(只关注展示),这样就可以简化f映射关系, 让UI = f(state)这个表达式更彻底

约束状态的变更。Redux 要求通过dispatch+reducer, mobx 要求数据变更函数使用action装饰或放在flow函数中,目的就是让状态的变更根据可预测性

单向数据流。数据流总是按照 Store -> View -> Store 这样的方式流动, 简化数据流


但是, React 的状态管理方案太多了,选择这些方案可能会让人抓狂,你需要权衡很多东西:

面向对象还是函数式还是函数响应式");

单 Store 还是多 Store?

不可变数据还是可变数据?

写代码爽还是后期维护爽");

自由还是约束?

强类型还是弱类型?

范式化数据还是非范式化?

React 原生还是第三方");

...




你不需要状态管理

对于大部分简单的应用和中后台项目来说是不需要状态管理的。说实话这些应用和传统 web 页面没什么区别, 每个页面都各自独立,每次打开一个新页面时拉取最新数据,增删改查仅此而已. 对于这些场景 React 的组件状态就可以满足, 没有必要为了状态管理而状态管理. 这种各自独立的‘静态’页面,引入状态管理就是过度设计了。

在考虑引入状态管理之前考虑一下这些手段是否可以解决你的问题:

是否可以通过抬升 State 来实现组件间通信");

如果跨越的层级太多,数据是否可以通过 Context API 来实现共享");

一些全局状态是否可以放在 localStorage 或 sessionStorage 中?

数据是否可以通过外置的事件订阅器进行共享");

...




你不需要复杂的状态管理

当你的应用有以下场景时,就要开始考虑状态管理:

组件之间需要状态共享。同一份数据需要响应到多个视图,且被多个视图进行变更

需要维护全局状态,并在他们变动时响应到视图

数据流变得复杂,React 组件本身已经无法驾驭。例如跨页面的用户协作

需要统一管理应用的状态。比如实现持久化,可恢复,可撤销/重做

...

首先确定是否需要 Redux、Mobx 这些复杂的状态管理工具");优先选择这些原生态的状态管理方式。

例如: 简单的使用 Context API 来做状态管理:

最近 hooks 用得比较爽(参考上一篇文章: 组件的思维),我就想配合 Context API 做一个状态管理器, 后来发现早就有人这么干了: unstated-next, 代码只有 38 行(Hooks+Context),接口非常简单:

依赖于 hooks 本身灵活的特性,我们可以用它来做很多东西, 仅限于想象力. 例如异步数据获取:

抑或者实现 Redux 的核心功能:


总结一下使用 hooks 作为状态管理器的优点:

极简。如上

可组合性. hooks 只是普通函数, 可以组合其他 hooks,以及其他Hooks Container. 上一篇文章提到 hooks 写着写着很像组件,组件写着写着很像 hooks,在用法上组件可以认为是一种"特殊"的 hooks。相比组件, hooks 有更灵活的组合特性

以 react 之名. 基于 Context 实现组件状态共享,基于 hooks 实现状态管理, 这个方式足够通用.

hooks 很多灵活的特性足以取代类似 Mobx 这些框架的大部分功能

只是普通的 React 组件,可以在 React inspector 上调试

强类型

基于Context API更容易实现模块化(或者分形)


需要注意的地方

没有外置的状态. 状态在组件内部,没有方法从外部触发状态变更

缺少约束. 是好处也是坏处, 对于团队和初学者来说没有约束会导致风格不统一,无法控制项目熵增。好处是可以自定义自己的约束

性能优化. 需要考虑 Context 变更带来的性能问题

调试体验不如 Redux

没有数据镜像, 不能实现诸如事件管理的需求

没有 Redux 丰富的生态

所以 Context+ Hooks 可以用于满足简单的状态管理需求, 对于复杂的状态管理需求还是需要用上 Redux、Mobx 这类专业的状态管理器.


其他类似的方案

unstated unstated-next 的前身,使用 setState API

react-hooks-global-state

扩展

React Context API — A Replacement for Redux");

unstated: 可能是简单状态管理工具中最好的



Redux

unstated 是一个极简的状态管理方案,其作者也说了不要认为unstated 是一个 Redux killer, 不要在它之上构建复杂的工具,也就是不要重复造轮子。所以一般到了这个地步, 其实你就应该考虑 Redux、Mobx、Rxjs 这些复杂的状态管理框架了。

Redux 是学习 React 绕不过的一个框架. 尽管 Redux 的代码只有一百多行,概念却很多,学习曲线非常陡峭,看官方文档就知道了。即使它的实现很简洁,但是开发代码并不简洁(和 mobx 相反, 脏活留给开发者),尤其遵循它的"最佳实践",是从头开始构建一个项目是非常繁琐的. 还在现在有类似 dva 或 rematch 这样的二次封装库来简化它.

本文不打算深入介绍 Redux 的相关实践, 社区上面有非常多的教程,官方文档也非常详尽. 这里会介绍 Redux 的主要架构和核心思想,以及它的适用场景.

Redux 的主要结构如上,在此之前你先要搞清楚 Redux 的初衷是什么,才能明白它为什么要这么设计. 在我看来 Redux 主要为了解决以下两个问题:

    可预测状态

    简化应用数据流


其实这也是 flux 的初衷, 只是有些它有些东西没做好. 明白 Redux 的初衷,现在来看看它的设计就会清晰很多

单一数据源 -> 可预测,简化数据流:数据只能在一个地方被修改

可以简化应用数据流. 解决传统多 model 模型数据流混乱问题(比如一个 model 可以修改其他 model,一个 view 受到多个 model 驱动),让数据变动变得可预测可调试

同构化应用开发

方便调试

方便做数据镜像. 可以实现撤销/重做、时间旅行、热重载、状态持久化和恢复

单向数据流 -> 简化数据流, 可预测

不能直接修改状态 -> 可预测

只能通过 dispatch action 来触发状态变更. action 只是一个简单的对象, 携带事件的类型和 payload

reducer 接收 action 和旧的 state, 规约生成新的 state. reducer 只是一个纯函数,可以嵌套组合子 reducer 对复杂 state 树进行规约

不可变数据.

可测试.

范式化和反范式化. Store 只存储范式化的数据,减少数据冗余。视图需要的数据通过 reselect 等手段反范式化

通过中间件隔离副作用 -> 可预测 可以说 Redux 的核心概念就是 reducer,然而这是一个纯函数。为了实现复杂的副作用,redux 提供了类似 Koa 的中间件机制,实现各种副作用. 比如异步请求. 除此之外,可以利用中间件机制,实现通用的业务模式, 减少代码重复。

Devtool -> 可预测。通过开发者工具可以可视化数据流


什么时候应该使用 Redux");

首先还是警告一下: You Might Not Need Redux, Redux 不是你的第一选择。

当我们需要处理复杂的应用状态,且 React 本身无法满足你时. 比如:

你需要持久化应用状态, 这样你可以从本地存储或服务器返回数据中恢复应用

需要实现撤销重做这些功能

实现跨页面的用户协作

应用状态很复杂时

数据流比较复杂时

许多不相关的组件需要共享和更新状态

外置状态

...


最佳实践

个人觉得react-boilerplate是最符合官方‘最佳实践’的项目模板. 它的应用工作流如下:

特性:

    整合了 Redux 生态比较流行的方案: immer(不可变数据变更),redux-saga(异步数据流处理),reselect(选取和映射 state,支持 memo,可复合),connected-react-router(绑定 react-router v4)

    根据页面分割 saga 和 reducer。见下面

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/6902.html

相关文章

  • 两年React老兵的总结 - 类型检查篇

    摘要:系列引言最近准备培训新人为了方便新人较快入手开发并编写高质量的组件代码我根据自己的实践经验对组件设计的相关实践和规范整理了一些文档将部分章节分享了出来由于经验有限文章可能会有某些错误希望大家指出互相交流由于篇幅太长所以拆分为几篇文章主要有以 系列引言 最近准备培训新人, 为了方便新人较快入手 React 开发并编写高质量的组件代码, 我根据自己的实践经验对React 组件设计的相关实践...

    scola666 评论0 收藏0
  • 前端每周清单半年盘点之 ReactReactNative 篇

    摘要:前端每周清单半年盘点之与篇前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点分为新闻热点开发教程工程实践深度阅读开源项目巅峰人生等栏目。与求同存异近日,宣布将的构建工具由迁移到,引发了很多开发者的讨论。 前端每周清单半年盘点之 React 与 ReactNative 篇 前端每周清单专注前端领域内容,以对外文资料的搜集为主,帮助开发者了解一周前端热点;分为...

    Barry_Ng 评论0 收藏0
  • 两年React老兵的总结 - 如何组织React项目

    摘要:一个复杂的应用都是由简单的应用发展而来的随着越来越多的功能加入项目代码就会变得越来越难以控制本文章主要探讨在大型项目中如何对组件进行组织让项目具备可维护性系列目录类型检查组件的组织样式的管理组件的思维状态管理目录组件设计的基本原则基本原则高 一个复杂的应用都是由简单的应用发展而来的, 随着越来越多的功能加入项目, 代码就会变得越来越难以控制. 本文章主要探讨在大型项目中如何对组件进行组...

    hoohack 评论0 收藏0
  • React 进阶设计与控制权问题

    摘要:盘点一下,模式反应了典型的控制权问题。异步状态管理与控制权提到控制权话题,怎能少得了这样的状态管理工具。状态管理中的控制主义和极简主义了解了异步状态中的控制权问题,我们再从全局角度进行分析。 控制权——这个概念在编程中至关重要。比如,轮子封装层与业务消费层对于控制权的争夺,就是一个很有意思的话题。这在 React 世界里也不例外。表面上看,我们当然希望轮子掌控的事情越多越好:因为抽象层...

    superw 评论0 收藏0
  • React 进阶设计与控制权问题

    摘要:盘点一下,模式反应了典型的控制权问题。异步状态管理与控制权提到控制权话题,怎能少得了这样的状态管理工具。状态管理中的控制主义和极简主义了解了异步状态中的控制权问题,我们再从全局角度进行分析。 控制权——这个概念在编程中至关重要。比如,轮子封装层与业务消费层对于控制权的争夺,就是一个很有意思的话题。这在 React 世界里也不例外。表面上看,我们当然希望轮子掌控的事情越多越好:因为抽象层...

    rubyshen 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<