精读《React 高阶组件》

本期精读文章是:React Higher Order Components in depth

1 引言

高阶组件( higher-order component ,HOC )是 React 中复用组件逻辑的一种进阶技巧。它本身并不是 React 的 API,而是一种 React 组件的设计理念,众多的 React 库已经证明了它的价值,例如耳熟能详的 react-redux。

高阶组件的概念其实并不难,我们能通过类比高阶函数迅速掌握。高阶函数是把函数作为参数传入到函数中并返回一个新的函数。这里我们把函数替换为组件,就是高阶组件了。

const EnhancedComponent = higherOrderComponent(WrappedComponent);

当然了解高阶组件的概念只是万里长征第一步,精读文章在阐述其概念与实现外,也强调了其重要性与局限性,以及与其他方案的比较,让我们一起来领略吧。

2 内容概要

高阶组件常见有两种实现方式,一种是 Props Proxy,它能够对 WrappedComponent 的 props 进行操作,提取 WrappedComponent state 以及使用其他元素来包裹 WrappedComponent。Props Proxy 作为一层代理,具有隔离的作用,因此传入 WrappedComponent 的 ref 将无法访问到其本身,需要在 Props Proxy 内完成中转,具体可参考以下代码,react-redux 也是这样实现的。

此外各个 Props Proxy 的默认名称是相同的,需要根据 WrappedComponent 来进行不同命名。

function ppHOC(WrappedComponent) {return class PP extends React.Component {// 实现 HOC 不同的命名static displayName = `HOC(${WrappedComponent.displayName})`;getWrappedInstance() {return this.wrappedInstance;}// 实现 ref 的访问setWrappedInstance(ref) {this.wrappedInstance = ref;}render() {return <WrappedComponent {...this.props,ref: this.setWrappedInstance.bind(this),} />}}
}@ppHOC
class Example extends React.Component {static displayName = 'Example';handleClick() { ... }...
}class App extends React.Component {handleClick() {this.refs.example.getWrappedInstance().handleClick();}render() {return (<div><button onClick={this.handleClick.bind(this)}>按钮</button><Example ref="example" /></div>  );}
}

另一种是 Inheritance Inversion,HOC 类继承了 WrappedComponent,意味着可以访问到 WrappedComponent 的 state、props、生命周期和 render 等方法。如果在 HOC 中定义了与 WrappedComponent 同名方法,将会发生覆盖,就必须手动通过 super 进行调用了。通过完全操作 WrappedComponent 的 render 方法返回的元素树,可以真正实现渲染劫持。这种方案依然是继承的思想,对于 WrappedComponent 也有较强的侵入性,因此并不常见。

function ppHOC(WrappedComponent) {return class ExampleEnhance extends WrappedComponent {...componentDidMount() {super.componentDidMount();}componentWillUnmount() {super.componentWillUnmount();}render() {...return super.render();}}
}

3 精读

本次提出独到观点的同学有:
@monkingxue @alcat2008 @淡苍 @camsong,精读由此归纳。

HOC 的适用范围

对比 HOC 范式 compose(render)(state) 与父组件(Parent Component)的范式 render(render(state)),如果完全利用 HOC 来实现 React 的 implement,将操作与 view 分离,也未尝不可,但却不优雅。HOC 本质上是统一功能抽象,强调逻辑与 UI 分离。但在实际开发中,前端无法逃离 DOM ,而逻辑与 DOM 的相关性主要呈现 3 种关联形式:

  • 与 DOM 相关,建议使用父组件,类似于原生 HTML 编写
  • 与 DOM 不相关,如校验、权限、请求发送、数据转换这类,通过数据变化间接控制 DOM,可以使用 HOC 抽象
  • 交叉的部分,DOM 相关,但可以做到完全内聚,即这些 DOM 不会和外部有关联,均可

DOM 的渲染适合使用父组件,这是 React JSX 原生支持的方式,清晰易懂。最好是能封装成木偶组件(Dumb Component)。HOC 适合做 DOM 不相关又是多个组件共性的操作。如 Form 中,validator 校验操作就是纯数据操作的,放到了 HOC 中。但 validator 信息没有放到 HOC 中。但如果能把 Error 信息展示这些逻辑能够完全隔离,也可以放到 HOC 中(可结合下一小节 Form 具体实践详细了解)。
数据请求是另一类 DOM 不相关的场景,react-refetch 的实现就是使用了 HOC,做到了高效和优雅:

connect(props => ({usersFetch: `/users?status=${props.status}&page=${props.page}`,userStatsFetch: { url: `/users/stats`, force: true }
}))(UsersList)

HOC 的具体实践

HOC 在真实场景下的运行非常多,之前笔者在 基于 Decorator 的组件扩展实践 一文中也提过使用高阶组件将更细粒度的组件组合成 Selector 与 Search。结合精读文章,这次让我们通过 Form 组件的抽象来表现 HOC 具有的良好扩展机制。

Form 中会包含各种不同的组件,常见的有 Input、Selector、Checkbox 等等,也会有根据业务需求加入的自定义组件。Form 灵活多变,从功能上看,表单校验可能为单组件值校验,也可能为全表单值校验,可能为常规检验,比如:非空、输入限制,也可能需要与服务端配合,甚至需要根据业务特点进行定制。从 UI 上看,检验结果显示的位置,可能在组件下方,也可能是在组件右侧。

直接裸写 Form,无疑是机械而又重复的。将 Form 中组件的 value 经过 validator,把 value,validator 产生的 error 信息储存到 state 或 redux store 中,然后在 view 层完成显示。这条路大家都是相同的,可以进行复用,只是我们面对的是不同的组件,不同的 validator,不同的 view 而已。对于 Form 而言,既要满足通用,又要满足部分个性化的需求,以往单纯的配置化只会让使用愈加繁琐,我们所需要抽象的是 Form 功能而非 UI,因此通过 HOC 针对 Form 的功能进行提取就成为了必然。

image

至于 HOC 在 Form 上的具体实现,首先将表单中的组件(Input、Selector…)与相应 validator 与组件值回调函数名(trigger)传入 Decorator,将 validator 与 trigger 相绑定。Decorator 完成了各种不同组件与 Form 内置 Store 间 value 的传递、校验功能的抽象,即精读文章中提到 Props Proxy 方式的其中两种作用:提取 state操作 props

function formFactoryFactory({validator,trigger = 'onChange',...
}) {return FormFactory(WrappedComponent) {return class Decorator extends React.Component {getBind(trigger, validator) {...}render() {const newProps = {...this.props,[trigger]: this.getBind(trigger, validator),...}return <WrappedComponent {...newProps} />}}}
}// 调用
formFactoryFactory({validator: (value) => {return value !== '';}
})(<Input placeholder="请输入..." />)

当然为了考虑个性化需求,Form Store 也向外暴露很多 API,可以直接获取和修改 value、error 的值。现在我们需要对一个表单的所有值提交到后端进行校验,根据后端返回,分别列出各项的校验错误信息,就需要借助相应项的 setError 去完成了。

这里主要参考了 rc-form 的实现方式,有兴趣的读者可以阅读其源码。

import { createForm } from 'rc-form';class Form extends React.Component {submit = () => {this.props.form.validateFields((error, value) => {console.log(error, value);});}render() {const { getFieldError, getFieldDecorator } = this.props.form;const errors = getFieldError('required');return (<div>{getFieldDecorator('required', {rules: [{ required: true }],})(<Input />)}{errors ? errors.join(',') : null}<button onClick={this.submit}>submit</button></div>);}
}export createForm()(Form);

4 总结

React 始终强调组合优于继承的理念,期望通过复用小组件来构建大组件使得开发变得简单而又高效,与传统面向对象思想是截然不同的。高阶函数(HOC)的出现替代了原有 Mixin 侵入式的方案,对比隐式的 Mixin 或是继承,HOC 能够在 Devtools 中显示出来,满足抽象之余,也方便了开发与测试。当然,不可过度抽象是我们始终要秉持的原则。希望读者通过本次阅读与讨论,能结合自己具体的业务开发场景,获得一些启发。

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

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

相关文章

【QT+QGIS跨平台编译】之五十三:【QGIS_CORE跨平台编译】—【qgssqlstatementparser.cpp生成】

文章目录 一、Bison二、生成来源三、构建过程一、Bison GNU Bison 是一个通用的解析器生成器,它可以将注释的无上下文语法转换为使用 LALR (1) 解析表的确定性 LR 或广义 LR (GLR) 解析器。Bison 还可以生成 IELR (1) 或规范 LR (1) 解析表。一旦您熟练使用 Bison,您可以使用…

transformers文本相似度

在自然语言处理(NLP)中,文本相似度是衡量两个文本之间语义或结构相似程度的一个重要概念。计算文本相似度的方法多种多样,适应不同的应用场景和需求。以下是一些常见的文本相似度计算方法: 1、余弦相似度: 通过将文本转换为向量表示(例如,使用词袋模型、TF-IDF 或 wor…

2024年个人护理赛道选品风向在哪?这份赛盈分销选品攻略必看!

2024年还会卷下去吗&#xff1f;看到一位行业大佬分享的内容深有感触&#xff1a;坚定做好产品&#xff0c;不做大卖&#xff0c;就不存在卷不卷。 有人出局&#xff0c;也会有人入局&#xff0c;并且深耕领域做大做强。 专注口腔护理的Bitvae入行不到两年&#xff0c;凭借一款…

C#学习(十四)——垃圾回收、析构与IDisposable

一、何为GC 数据是存储在内存中的&#xff0c;而内存又分为Stack栈内存和Heap堆内存 Stack栈内存Heap堆内存速度快、效率高结构复杂类型、大小有限制对象只能保存简单的数据引用数据类型基础数据类型、值类型- 举个例子 var c new Customer{id: 123,name: "Jack"…

Java中String类有哪些常用方法?

Java中的String类提供了许多有用的方法&#xff0c;用于处理字符串。以下是一些常用的方法及其简要描述&#xff1a; 1. **charAt(int index)**&#xff1a;返回指定位置的字符。 2. **length()**&#xff1a;返回字符串的长度。 3. **substring(int beginIndex, int endInd…

微信小程序手势冲突?不存在的!

原生的应用经常会有页面嵌套列表&#xff0c;滚动列表能够改变列表大小&#xff0c;然后还能支持列表内下拉刷新等功能。看了很多的小程序好像都没有这个功能&#xff0c;难道这个算是原生独享的吗&#xff0c;难道是由于手势冲突无法实现吗&#xff0c;冷静的思考了一下&#…

Google验证码,扫描绑定,SpringBoot+ vue

文章目录 后端1.使用Google工具类这个 类的 verifyTest 方法可以判断扫描绑定之后的app上面验证码的准确性。这个类通过g_user,g_code(就是谷歌验证器的secret,这个你已经插入到数据库 中)来生成相关二维码。2.用工具类自带的g_user,g_code来生成二维码2.1通过请求来生成相关二…

你知道vector底层是如何实现的吗?

你知道vector底层是如何实现的吗&#xff1f; vector底层使用动态数组来存储元素对象&#xff0c;同时使用size和capacity记录当前元素的数量和当前动态数组的容量。如果持续的push_back(emplace_back)元素&#xff0c;当size大于capacity时&#xff0c;需要开辟一块更大的动态…

【InternLM 实战营笔记】XTuner 大模型单卡低成本微调实战

XTuner概述 一个大语言模型微调工具箱。由 MMRazor 和 MMDeploy 联合开发。 支持的开源LLM (2023.11.01) InternLM Llama&#xff0c;Llama2 ChatGLM2&#xff0c;ChatGLM3 Qwen Baichuan&#xff0c;Baichuan2 Zephyr 特色 傻瓜化&#xff1a; 以 配置文件 的形式封装了大…

WebGIS----wenpack

学习资料&#xff1a;https://webpack.js.org/concepts/ 简介&#xff1a; Webpack 是一个现代化的 JavaScript 应用程序的模块打包工具。它能够将多个 JavaScript 文件和它们的依赖打包成一个单独的文件&#xff0c;以供在网页中使用。 Webpack 还具有编译和转换其他类型文…

自学新标日第六课(单词部分 未完结)

第六课 单词 单词假名声调词义来月らいげつ1下个月先月せんげつ1上个月夜中よなか3午夜昨夜ゆうべ0昨天晚上コンサートこんさーと1音乐会クリスマスくりすます3圣诞季誕生日たんじょうび&#xff13;生日こどもの日こどものひ&#xff15;儿童节夏休みなつやすみ&#xff13;…

看待事物的层与次 | DBA与架构的一次对话交流

前言 在计算机软件业生涯中,想必行内人或多或少都能感受到系统架构设计与数据库系统工程的重要性,也能够清晰地认识到在计算机软件行业中技术工程师这个职业所需要的专业素养和必备技能! 背景 通过自研的数据库监控管理工具,发现 SQL Server 数据库连接数在1-2K之间,想…

Yii2中如何使用scenario场景,使rules按不同运用进行字段验证

Yii2中如何使用scenario场景&#xff0c;使rules按不同运用进行字段验证 当创建news新闻form表单时&#xff1a; 添加新闻的时候执行create动作。 必填字段&#xff1a;title-标题&#xff0c;picture-图片&#xff0c;description-描述。 这时候在model里News.php下rules规则…

星座每日运势 api接口

接口数据api 接口平台&#xff1a;https://api.yuanfenju.com/ 开发文档&#xff1a;https://doc.yuanfenju.com/zhanbu/yunshi.html 支持格式&#xff1a;JSON 请求方式&#xff1a;HTTP POST <?php//您的密钥 $api_secret "wD******XhOUW******pvr"; //请…

利用coze 搭建“全功能“微信客服(2)

紧跟上篇 利用coze 搭建"全功能"微信客服&#xff08;1&#xff09;&#xff0c;不知道来龙去脉自行查阅 先表扬下coze: coze 是国内少数开放平台之一&#xff0c;里面提供各种插件还可以开发工作流&#xff0c;让你可以实现多模态全功能大模型 吐槽 没有API开放接口…

国外最流行的是AI,国内最流行的是AI培训教程

国外最流行的是AI&#xff0c;国内最流行的是AI培训教程。 最近李一舟AI教程事件&#xff0c;验证了这句话。 如今给客户做方案项目里能加点AI色彩&#xff0c;立项的成功率都变大(特别是事业单位)。 正因如此&#xff0c;大家都在狂补AI的知识&#xff0c;不然肚子里没点墨水&…

2024亚马逊全球开店注册前需要准备什么?

在2023年出海四小龙SHEIN、Temu、速卖通AliExpress、TikTok Shop快速增长扩张&#xff0c;成为了中国跨境卖家“逃离亚马逊”的新选择。但是&#xff0c;跨境电商看亚马逊。当前&#xff0c;亚马逊仍然是跨境电商行业的绝对老大&#xff0c;占有将近70%成以上的业务份额。 作为…

threejs显示本地硬盘上的ply文件,通过webapi

由于ply文件是第三方提供的&#xff0c;threejs无法用绝路路径的方式显示ply 所以想通过webapi把ply通过url地址的方式给threejs 1.webapi部分 /// <summary>/// 获取PLY文件/// </summary>/// <returns></returns>[HttpPost(Name "GetPly&qu…

分享fastapi低级错误

我是创建表的时候把__tablename__ 写成__table__然后一直报这个错误

Android Activity跳转详解

在Android应用程序中&#xff0c;Activity之间的跳转是非常常见的操作&#xff0c;通过跳转可以实现不同界面之间的切换和交互。在本篇博客中&#xff0c;我们将介绍Android中Activity跳转的相关知识&#xff0c;包括基本跳转、传递参数、返回数据以及跳转到浏览器、拨号应用和…