当设计模式遇上 Hooks

简介: 数据结构与设计模式能够指导我们在开发复杂系统中寻得一条清晰的道路,既然都说 Hooks 难以维护,那就尝试让「神」来拯救这混乱的局面。对于「设计模式是否有助于我们写出更优雅的 Hooks 」这个问题,看完本文,相信你心中也会有自己的答案。

微信图片_20210830162611.jpg

作者 | 阿晨
来源 | 阿里技术公众号

一 前言

「设计模式」是一个老生常谈的话题,但更多是集中在面向对象语言领域,如 C++,Java 等。前端领域对于设计模式的探讨热度并不是很高,很多人觉得对于 JavaScript 这种典型的面向过程的语言来说,设计模式的价值很难体现。之前我持有类似的观点,对于设计模式的理解仅停留在概念层面,没有深入去了解其在前端工程中的实践。近期阅读了《 JavaScript 设计模式与开发实践》一书,书中介绍了 15 种常见设计模式和基本的设计原则,以及如何使用 JavaScript 优雅实现并应用于实际工程中。碰巧前不久团队举行了一场关于 Hooks 的辩论赛,而 Hooks 的核心思想在于函数式编程,于是决定探究一下「设计模式是否有助于我们写出更优雅的 Hooks 」这一话题。

二 为什么是设计模式

在逆袭武侠剧中,主人公向第一位师父请教武艺时,最开始老师父只会让主人公挑水、扎马步等基本功,主人公这时总是会诸般抱怨,但又由于某些客观原因又不得不坚持,之后开始学习真正的武艺时才顿悟之前老师父的一番苦心,夯实基础后学习武艺突飞猛进,最终成为一代大侠。对于我们开发者来说,「数据结构」和「设计模式」就是老师父所教的基本功,它不一定能够让我们走得更快,但一定可以让我们走得更稳、更远,有助于我们写出高可靠且易于维护的代码,避免日后被 “挖坟”。

在 Hooks 发布以来,饱受诟病的一点就是维护成本激增,特别是对于成员能力水平差距较大的团队来说。即便一开始由经验老到的同学搭建整个项目框架,一旦交由新人维护一段时间后,大概率也会变得面目全非,更不用说让新人使用 Hooks 开发从零到一的工程。我理解这是由于 Hooks 的高度灵活性所导致的,Class Component 尚有一系列生命周期方法来约束,而 Hooks 除了 API 参数上的约束,也仅有 “只在最顶层使用 Hook” “只在 React 函数中调用 Hook” 两条强制规则。另一方面自定义 Hook 提高组件逻辑复用率的同时,也导致经验不足的开发者在抽象时缺少设计。Class Component 中对于逻辑的抽象通常会抽象为纯函数,而 Hooks 的封装则可能携带各种副作用(useEffect),出现 bug 时排查成本较大。

那么既然「设计模式」是一种基本功,而「Hooks」是一种新招式,那我们就尝试从设计模式出发,攻克新招式。

三 有哪些经典的设计模式

在正式进入话题之前,我们先简单回顾一下那些快被我们遗忘的经典设计模式和设计原则。日常中,我们提到设计原则都会将其简化为「SOLID」,对应于单一职责原则(Single Responsibility Principle)、开放/封闭原则(Open Closed Principle)、里氏替代原则(Liskov Substitution Principle)、最小知道原则(Law of Demeter)、接口隔离原则(Interface Segregation Principle)和依赖倒置原则(Dependence Inversion Principle)。设计模式又包括了单例模式、策略模式、代理模式、迭代器模式、发布-订阅模式、命令模式、组合模式、模版方法模式、亨元模式、职责链模式、中介者模式、装饰者模式、状态模式、适配器模式等。

关于设计原则和设计模式有很多优秀的讲解文章,这里就不过多赘述了。

四 1 + 1 > 2

1 非得用 useContext 吗

在 React Hook 工程中,一旦涉及到全局状态管理,我们的直觉会是使用 useContext。举个例子,假设工程中需要根据灰度接口返回的信息,决定某些组件是否进行渲染。由于整个工程共享一份灰度配置,我们很容易就想到将其作为一个全局状态,在工程初始化时调用异步接口获取并进行初始化,然后在组件内部使用useContext来获取。

111.png

222.png

但是 createContext 的使用会造成一旦全局状态发生变更,Provider 下的所有组件都会进行重新渲染,哪怕它没有消费 context 下的任何信息。

333.gif

仔细想想,这种场景和设计模式中的“发布-订阅模式”有着异曲同工之处,我们可以自己定义一个全局状态实例 GrayState,在 App 组件中初始化值,在子组件中订阅该实例的变化,也能够达到相同的效果,并且仅订阅了 GrayState 变化的组件会进行重新渲染。

444.png

555.png

666.png

最终实现的效果是一致的,不同的是获取灰度状态后,仅仅依赖灰度配置信息的 GrayComponent 进行了重新渲染。

666.gif

考虑更好复用的话我们还可以将对 Status 监听的部分抽象为一个自定义 Hook:

33.png

c.png

当然,借助 redux 也是能够做到按需重新渲染,但如果项目中并没有大量全局状态的情况下,使用 redux 就显得有点杀鸡用牛刀了。

2 useState 还是 useReducer

Hooks 初学者常常会感慨 “我开发中只用到 useStateuseEffect,其它钩子似乎不怎么需要的样子”。这种感慨源于对 Hooks 的理解还不够透彻。useCallbackuseMemo 是一种在必要时刻才使用的性能优化钩子,平常接触较少也是有可能的,但 useReducer 却值得我们重视。在官方的解释中,useReducer 是 useState 的替代方案,什么情况下值得替代呢,这里同样以一个例子来分析。

举个状态模式中最为常见的例子 —— 音乐播放器的顺序切换器。

1111.png

在上面的实现中,可以看到模式的切换依赖于上一个状态,在“顺序播放-随机播放-循环播放”三个模式中依次切换。目前只有三种模式,可以使用简单的 if...else 方式实现,但一旦模式多了便会十分难以维护和扩展,因此,针对这种行为依赖于状态的场景,当分支增长到一定程度时,便需要考虑使用“状态模式”重新设计。

11.png

22.png

React 官网中对 useReducer 的解释中提到「在某些场景下,useReducer 会比 useState 更适用,例如 state 逻辑较复杂且包含多个子值,或者下一个 state 依赖于之前的 state 等」。这里着重看一下后一个场景,「类的行为是基于它的状态改变」的“状态模式”就是一种典型的依赖上一个状态的场景,这使 useReducer 天然的适用于多状态切换的业务场景。

44.png

3 自定义 Hook 封装原则

自定义 Hook 是 React Hook 广受欢迎的重要原因,然而一个抽象不佳的自定义 Hook 却可能极大增加了维护成本。在《The Ugly Side of React Hooks 》“The hidden side effect” 章节中就列举了一个层层嵌套的副作用带来的异常排查成本。我们经常说设计原则和设计模式有助于提高代码的可维护性和可扩展性,那么有哪些原则/模式能够帮助我们优雅地封装自定义 Hooks 呢?

OS:“如何优雅地封装自定义 Hooks” 是一个很大的话题,这里仅仅抛转引玉讲述几个观点。

第零要义:存在数据驱动

在比较 Hooks 和类组件开发模式时,常常提到的一点就是 Hooks 有助于我们在组件间实现更广泛的功能复用。于是,刚开始学习 Hooks 时,对于任何可能有复用价值的功能逻辑,我经常矫枉过正地封装成奇奇怪怪的 Hooks,比如针对「在用户关闭通知且当天第一次打开时,进行二次提醒打开」这么一个功能,我抽象了一个 useTodayFirstOpen:

55.png

66.png

事实上,它并没有返回任何东西,在组件内调用时也仅仅是 useTodayFirstOpen() 。回过头来,这块功能并没有任何的外部数据流入,也没有数据流出,完全可以将其抽象为一个高阶函数,而不是一个自定义 Hooks。因此具有复用价值且与外部存在数据驱动关系的功能模块才有必要抽象为自定义 Hooks。

第一要义:单一职责原则

单一职责原则(SRP)建议一个方法仅有一个引起变更的原因。自定义 Hooks 本质上就是一个抽象方法,方便实现组件内逻辑功能的复用。但是如果一个 Hooks 承担了太多职责,则可能造成某一个职责的变动会影响到另一个职责的执行,造成意想不到的后果,也增加了后续功能迭代过程中出错的概率。至于什么时候应该拆分,参照 SRP 中推荐的职责分离原则,在 Hooks 中更适合解释为「如果引起两个数据变更的原因不是同一个时,则应该将两者分离」。

以 useTodayFirstOpen 为例,假设外界还有 Switch 控件需要根据 status 做展示与交互:

  const [status, setStatus] = useState();const [isTodayFirstOpen, setIsTodayFirstOpen] = useState(false);// ...const updateStatus = async (val) => {const res = await updateUserStatus(val);// dosomething...}return [status, updateStatus];
}

假设 getUserStatus 的返回格式发生了改变,需要修改该 Hook。

  const [status, setStatus] = useState();const [isTodayFirstOpen, setIsTodayFirstOpen] = useState(false);useEffect(() => {// 获取用户状态const fetchStatus = async () => {const res = await getUserStatus();setStatus(res.notice);};fetchStatus();// ...}, []);// ...
}

假设再有一天,监管反馈每天二次提醒频率太高了,要求改为每周「二次提醒」,又需要再次重构该 Hook。

  const [status, setStatus] = useState();const [isThisWeekFirstOpen, setIsThisWeekFirstOpen] = useState(false);useEffect(() => {// 获取用户状态// ...// 判断今天是否首次打开const value = window.locaStorage.getItem('isThisWeekFirstOpen');if (!value) {setIsTodayFirstOpen(true);} else {const curr = getNowDate();setIsThisWeekFirstOpen(diffDay(curr, value) >= 7);}}, []);// ...
}

这显然违背了单一职责原则,此时需要考虑分离 status 和 ...FirstOpen 逻辑,使其更加通用,再以组合的形式抽象为业务 Hook。

a1.png

a2.png


 

a3.png

A4.png

A6.png

改造之后,如果获取/设置用户状态的接口发生变动,则修改 useUserStatus;如果二次提醒的效果需要改动(如上报日志),则修改 useSecondConfirm;如果业务上调整了二次提醒逻辑(会员不二次提醒),则仅需修改 useStatusWithSecondConfirm ,各自定义 Hooks 各司其职。

c.jpg

第 n + 1 要义努力探索中……

五 总结

数据结构与设计模式能够指导我们在开发复杂系统中寻得一条清晰的道路,那既然都说 Hooks 难以维护,那就尝试让「神」来拯救这混乱的局面。对于「设计模式是否有助于我们写出更优雅的 Hooks 」这个问题,看完前面的章节,相信你心中也有自己的答案,当然,本文并不是为了辩论「 Hooks 是否强于类开发」这一话题,如果有兴趣的话,欢迎评论留言~

原文链接
本文为阿里云原创内容,未经允许不得转载。 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/512531.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac 电脑android环境变量设置,mac上Android环境变量配置

1.AndroidSDK路径查看(1)AndroidStudio:菜单栏AndroidStudio > Preferences > Appearences&Behavior > System Settins > Android SDK tab中的SDK Location。(2)Eclipse:菜单栏Eclipse > Preferences > Android tab中的SDK Location2.环境变量配置&#…

PostgreSQL数据目录深度揭秘

简介: PostgreSQL是一个功能非常强大的、源代码开放的客户/服务器关系型数据库管理系统(RDBMS),被业界誉为“先进的开源数据库”,支持NoSQL数据类型,主要面向企业复杂查询SQL的OLTP业务场景,提供…

深入浅出 Spring 架构设计

作者 | 三太子敖丙来源 | 敖丙前言为什么需要Spring? 什么是Spring?对于这样的问题,大部分人都是处于一种朦朦胧胧的状态,说的出来,但又不是完全说的出来,今天我们就以架构设计的角度尝试解开Spring的神秘面纱。本篇文章以由浅入…

海云健康:上云为10万家药店带去了什么价值?

“全国每5个人里,就有1个正在接受海云健康系统提供的服务。” 在海云健康(以下简称“海云”)的系统后台上,每一分钟就有10万笔的买药订单涌动。也许很多人没有听过海云健康的名字,但当他们走进社区药店时,已经在享受海云的“存健康”药店会员管理系统提供的服务。 海云创办于…

pgsql数据库默认配置事务类型_postgreSql最佳配置详解(connection 申请、回收策略)...

一、引子合理配置一个应用的数据库参数,使其运行良好,这很重要。本文以某务中台的生产环境为例,从Apollo上拔下来一套配置,分析是否合理。二、MybatisPlus配置由于我们使用Apollo配置参数,所以分两部分:1.个…

android系统手势app,8种iOS手势规定和14种android手势规定详解

不知道大家对ios系统和android系统的规定的原生手势有哪些吗?看到这样的标题,你能够回答出几个呢?其实,APP设计师和h5开发工程师对移动设备的手势的了解和理解是非常有必要的。只有掌握了这些平台的手势规定才能设计出符合用户操作…

mPaas 运维流程介绍

简介: 金融级移动开发平台 mPaaS(Mobile PaaS)为 App 开发、测试、运营及运维提供云到端的一站式解决方案,能有效降低技术门槛、减少研发成本、提升开发效率,协助企业快速搭建稳定高质量的移动应用。在我们日常运维过程…

360借条通过CCRC权威认证,再获国家级认可

近日,中国网络安全审查技术与认证中心(CCRC)向360借条App颁发移动互联网应用程序(App)安全认证证书。通过该认证,表明360借条App在个人信息保护方面的工作再次取得了国家级肯定。 随着移动互联的蓬勃发展&…

在.NET环境中使用Python和TensorFlow进行深度学习入门篇

在.NET环境中使用Python和TensorFlow进行深度学习可能需要通过一些中介工具或者框架,因为TensorFlow原生支持的是Python、C等语言。以下是一种可能的入门步骤: 安装Python和TensorFlow: 首先,你需要在你的系统上安装Python&#x…

ElasticSearch IK 分词器快速上手

简介: ElasticSearch IK 分词器快速上手 一、安装 IK 分词器 1.分配伪终端 我的 ElasticSearch 是使用 Docker 安装的,所以先给容器分配一个伪终端.之后就可以像登录服务器一样直接操作docker 中的内容了docker exec -it 容器ID /bin/bash 2.使用 elasticsearch…

装完系统还要装什么_家里装了空调还要装空气净化系统吗?会不会太浪费了?...

微信搜一搜舒适11今天这篇文章,小壹就向大家科普一下空调和新风系统,告诉大家为什么装了空调还要装新风机。1、空调是什么? 对此大家都能够脱口而出:空调就是用来制冷或制热的机器,能够改变室内温度,让我们…

移动端性能优化系列—启动速度

简介: 移动端性能对用户体验、留存有着至关重要的影响,作为开发者是不是被这样吐槽过,“这个 APP 怎么这么大?”、“怎么一直在 APP 封面图转悠,点不进去”、“进入详情效果有些卡”、“用 4G 使用你们的 APP&#xff…

三重框架构建和威胁情报及时可达,山石网科发布StoneOS 5.5R9

升级的StoneOS 5.5R9版本,在预测与发现、防御与控制、检测与分析、响应与管理四个角度,通过云端运营中心的情报赋能和统筹运维,策略助手的访问链接发现,边界流量过滤的IP快速分类与阻断,精确边缘策略对用户与应用的精细…

html截取url字段,Html中截取url参数 实现HTML间的url传值

大家好:今天遇到一个问题,页面全是html,url传值,竟然获取不到参数值:A.html//登录按钮jQuery(function($) {$("#login").click(function() {$.ajax({//url:http://10.9.80.211:8090/iaf-platform-web/doLogi…

Apache Flink 在京东的实践与优化

简介: Flink 助力京东实时计算平台朝着批流一体的方向演进。 本文整理自京东高级技术专家付海涛在 Flink Forward Asia 2020 分享的议题《Apache Flink 在京东的实践与优化》,内容包括: 业务演进和规模容器化实践Flink 优化改进未来规划一、业…

云端攻防的最后战场,腾讯主机安全旗舰版发布

在刚刚过去的12月里,Apache Log4j 漏洞席卷全球,成为互联网安全领域暴热的话题。而Log4j的破坏力也十分惊人,全球数亿台设备都可能受到影响,攻击者仅需一段代码就可能远程控制服务器。而这场风波一直影响至今,几乎所有…

cad断点快捷键_CAD中打断于点的快捷键

展开全部Autocad部分快捷键绘图命令PO POINT 点L LINE 直线XL XLINE 构造线PL PLINE 多段线ML MLINE 多线SPL SPLINE 样条曲线POL POLYGON 正多边形REC RECTANGLE 矩形C CIRCLE 圆A ARC 圆弧DO DONUT 圆环EL ELLIPSE 椭圆REG REGION 面域T(MT) MTEXT 多行文本DT TEXT 单行文字3…

华为鸿蒙系统p40,华为鸿蒙OS系统正式亮剑!华为P40再次确认:双打孔+麒麟990+鸿蒙OS...

众所周知,华为Mate 系列、P系列产品一直都是华为高端旗舰机型,在整体外观设计、综合性能、拍照等方面,也都是华为最为顶尖的旗舰机型,但在售价方面却遭到了很多“性价比”用户的吐槽,纷纷吐槽华为Mate系列、P系列产品“…

Flink 在顺丰的应用实践

简介: 顺丰基于 Flink 建设实时数仓的思路,引入 Hudi On Flink 加速数仓宽表,以及实时数仓平台化建设的实践。 本⽂由社区志愿者苗文婷整理,内容源⾃顺丰科技大数据平台研发工程师龙逸尘在 Flink Forward Asia 2020 分享的《Flink…

搭建一个高可用的镜像仓库,这是我见过最详细、最简单的教程

作者 | 小碗汤来源 | 我的小碗汤今天分享一篇搭建一个高可用镜像仓库的教程。详细中夹杂着简单~。Harbor 部署架构图harbor 使用 helm 部署在 k8s 集群中,通过 ingress-nginx 代理。pgsql 采用 Pgpool-II 代理,做主从切换、通过同步流式复制进行数据复制…