写点什么

「React 开发」梳理 HOC 的点点滴滴

作者:叶一一
  • 2022-10-14
    北京
  • 本文字数:3632 字

    阅读完需:约 1 分钟

「React开发」梳理HOC的点点滴滴

由于不可避免的遗忘曲线和最初潦草学习等诸多因素,我获得的某些技术点会存在没有吃透或者出现了被遗忘的情况。幸运的是,我有经验积累这个增益 buff,能够不时的有新的开发思路诞生。

此消彼长,也不是长久之计。于是我想通过温故知新的方式,重拾部分技术知识或者提炼一些有趣的“奇思妙想”。

本篇主要重新梳理一下 React 的 HOC。

温故而知新,可以为师矣。

带着问题去寻找答案

如果我还是单纯的看一遍文档,到最后估计收获不大。所以,我在开始前,列了一些自己的疑问以及带着这些疑问要找到答案。

  • 为什么提起 HOC 总会看到 Mixins 的身影?

  • 横切关注点到底是什么?为什么文档里面提到 HOC 解决了横切关注点问题?

  • React 为什么用 HOC 替代 Mixins?

  • 为什么我平时用不上 HOC?

  • 我怎样才能用的上 HOC?

......

HOC 和它的兄弟 Mixins

先来聊一下 HOC 和 Mixins 这对兄弟的前情提要。

为了解决横切关注点问题,早先 React 也是采用 Mixins 方式,但是随着组件的增加,Mixins 方式带来了一系列的问题,比如隐式依赖、名称冲突、代码复杂性增加等。

所以 React 采用 HOC 的设计模式替代 Mixins 解决横切关注问题。

名词解释时间

横切关注点

React 文档里面提到

如果完全不同的组件有相似的功能,这就会产生“横切关注点(cross-cutting concerns)“问题

知乎文章《面向对象困境之 —— 横切关注点》,介绍了关注点和横切关注点,并举了日志功能的例子,写的非常好,我把前面的介绍贴出来,帮助大家更好理解。

什么是关注点(Concern)?

A Concern is a term that refers to a part of the system divided on the basis of the functionality.

关注点是指基于功能划分系统的一部分。

什么是横切关注点(Crosscutting Concern)?

部分关注点「横切」程序代码中的数个模块,即在多个模块中都有出现,它们即被称作「横切关注点(Cross-cutting concerns, Horizontal concerns)」。

这样说好像还是特别抽象?那我们举个例子。

日志功能就是横切关注点的一个典型案例。日志功能往往横跨系统中的每个业务模块,即“横切”所有需要日志功能的类和方法体。所以我们说日志成为了横切整个系统对象结构的关注点 —— 也就叫做横切关注点啦。

解决横切关注点不是提炼某个公共方法那么简单,公共方法具有功能的完整性,如果想要适配所有的业务模块,随着业务的迭代,公共方法中的代码会越来越复杂。所以 Vue 框架中采用 Mixins 功能,React 使用 HOC 替换 Mixins 来解决横切关注点问题。


Mixins

Vue官网中对 mixins 介绍如下

混入 (mixin) 提供了一种非常灵活的方式,来分发 Vue 组件中的可复用功能。一个混入对象可以包含任意组件选项。当组件使用混入对象时,所有混入对象的选项将被“混合”进入该组件本身的选项。

官网例子

// 定义一个混入对象var myMixin = {  created: function () {    this.hello()  },  methods: {    hello: function () {      console.log('hello from mixin!')    }  }}
// 定义一个使用混入对象的组件var Component = Vue.extend({ mixins: [myMixin]})
var component = new Component() // => "hello from mixin!"
复制代码

上面的例子通过 mixins 方式将对象 myMixin 的方法添加到 Component 组件上去。

HOC

React官网对 HOC 介绍如下

高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。

具体而言,高阶组件是参数为组件,返回值为新组件的函数。

从这两段话中,我们不难提炼出复用组件、高级技巧、设计模式、参数为组件、返回为新组件的函数等关键词,发散一下思维,HOC 允许开发者在定义一个特定的地方定义某个逻辑,并在许多组件之间共享它,进而解决横切关注点的问题。


Mixins 和 HOC,为什么 React 选择了后者?

Mixins 的诞生很好的帮助解决了功能复用的问题,但是随着时间的推移,Mixins 也逐渐暴露出它的短板。

Mixins 的短板

React 对于 Mixins 带来的麻烦,有一篇文章文章地址)进行了详细的介绍,简单总结为一下几点。

引入隐式依赖

JavaScript 是一种动态语言,因此很难强制执行或记录这些依赖关系。一般组件可以引入多个 mixin,所以在使用某个 mixin 中的方法时,其实并不能够区分是使用的哪个 mixin 的方法,一旦有人移除了某个被引用的方法,而没有将所有依赖该方法的地方修改成正确的依赖,原本正常的功能就会发生错误。

这些隐含的依赖关系会给开发增加难度。


导致名称冲突

不能保证两个特定的 mixin 可以一起使用。例如,如果 FluxListenerMixin 定义了 handleChange(),而 WindowSizeMixin 定义了 handleChange(),则不能一起使用。也不能在自己的组件上定义具有此名称的方法。

另外,如果与第三方包中的 mixin 有名称冲突,不能只重命名它的方法。相反,必须在组件上使用笨拙的方法名称以避免冲突。


导致复杂性滚雪球

即使 mixin 一开始很简单,但随着时间的推移,它们往往会变得复杂。

一个 mixin 可以被多个组件引入,导致使用相同 mixin 的组件变得越来越耦合。任何新功能都会被添加到使用该 mixin 的所有组件中。如果不复制代码或在 mixin 之间引入更多的依赖关系和间接性,就无法拆分 mixin 的“更简单”部分,也就导致 mixin 的复杂性越来越高。


HOC 是怎么做的?

为了解决功能复用问题,同时为了规避 Mixins 产生的问题,HOC 做了以下设计。

透传 props

将与 HOC 自身无关的 props 透传,提升使用的灵活性,降低组件间的耦合性。

组合 HOC

将 HOC 与容器组件组合,不直接修改传入组件的 HOC,避免引入隐式依赖。

最大化可组合性

HOC 可以接收多个参数,这样可以降低单一参数带了的功能复杂性不断增加的问题。


HOC 的实际应用

名将的归宿是战场,技术的归宿是实践。

通过探索 HOC 的实际应用,也能能帮助解决“为什么我平时用不上 HOC?”和“我怎样才能用的上 HOC?”两个疑问。


我先想明白了为什么我平时用不上 HOC

因为我没有跳出思维定式的圈子,在早期的业务开发中,更多的是封装完整的功能组件,对于高阶组件没有理解的很透彻,一方面是不知道用在哪,另一方面是使用了可能也没有想到这种设计模式就是 HOC。

所以,我后来就开始注意,在实际开发中,哪些能用 HOC,以及使用之后,是否开发效率和代码可维护性变的更高。


哪些功能可以让你用上 HOC

一通百通,一悟千悟。

我将项目中用到 HOC 的功能进行了整理归纳,大致有以下应用场景。

注:是项目中用到的,并不全是我一个人开发的,有很多是我同事的智慧。


按钮权限控制

按钮权限控制几乎是后台管理系统必备的功能。


功能分析

用一句话概括一个基础的按钮权限控制功能,即不同角色用户可进行的页面操作可通过数据配置进行控制。


功能实现

新增了 PowerButton 组件

  • 因为每个页面的每个按钮都需要加权限控制,所以使用组合的方式,将权限组件包裹在按钮容器外侧达到控制的目的;

  • 缓存权限控制按钮数据,方便统一管理,该数据为页面 pathname 对应权限按钮数组的枚举;

  • 获取权限控制按钮数据,获取缓存中 powerButtonData 的值和当前页面的 pathname,进而获取当前页面的权限按钮数组;

  • 获取当前按钮的 code 值在权限按钮数组中的索引。如果索引值大于-1 表示当前按钮存在,页面也会展示按钮;反之则表示按钮不存在,页面不会展示该按钮;

/** * @description 按钮权限控制 */
import React from 'react';
class PowerButton extends React.Component { static propTypes = { code: PropTypes.func.isRequired, // 操作按钮的code值 };
constructor(props) { super(props); this.state = { powerIndex: -1, // 当前按钮在用户拥有的按钮列表中的索引 }; }
componentDidMount() { /** @name 缓存中的用户拥有的权限按钮数据 */ let powerButtonData = JSON.parse(sessionStorage.getItem(powerButtonData) || '{}'); /** @name 当前打开页面 */ const pathname = window.location.pathname; /** @name 权限按钮数组对象 */ const buttonList = powerButtonData[pathname]; if (buttonList) { const powerIndex = buttonList.findIndex(btn => btn.title === code); this.setState({ powerIndex, }); } }
render() { const { powerIndex } = this.state; const { children } = this.props; if (powerIndex > -1) { return <>{children}</>; } else { return null; } }}export default PowerButton;
复制代码

页面使用

我们目前使用的中文字符做为按钮的唯一值,这样方便业务方在后台配置每个页面的按钮权限。如果有英文字符容易配错。

import { PowerButton } from '@/components';
<PowerButton code='新增'> <Button type='primary'>新增</Button></PowerButton>
复制代码

总结

上面 HOC 的实际应用,是否觉得功能挺眼熟的,空闲的时候,不妨捋捋自己项目的代码,没准会有不错的收获。

文章写的很基础,是因为作者本人还处于不断学习的阶段。不过温故确实能够知新。

虽然作者本人的功力还没有足够深厚,但是正在通过温习学习收获成长。希望未来奔跑着,能追上一些大佬的背影。

发布于: 刚刚阅读数: 5
用户头像

叶一一

关注

掘金优秀创作者 2022-09-01 加入

非职业传道受业解惑前端程序媛,华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。

评论

发布
暂无评论
「React开发」梳理HOC的点点滴滴_前端_叶一一_InfoQ写作社区