前言
最近在对项目内的表单组件进行的优化,在实现高阶组件的过程中发现目前关于 react 高阶组件的文章都比较旧了,很多都是以类组件的形式去实现,与 Typescript 的结合使用也比较难找到示例。因此决定自己写一篇关于 Recat hook + Typescript 实现高阶组件的文章,希望简单直观的让大家学习到高阶组件的使用场景及封装方式。
什么是高阶组件
高阶组件(HOC)是 React 中用于复用组件逻辑的一种高级技巧。HOC 自身不是 React API 的一部分,它是一种基于 React 的组合特性而形成的设计模式。
高阶组件其实就是一个函数,这个 函数的参数是一个组件,返回值也是一个组件。功能也就是将传入的组件进行一些通用的处理,再将处理过的组件返回。举个例子:
const WithHelloWorld = (Component) => {const extraProps = 'hello world';const newComponent = (props) => {console.log(WithHelloWorld);return <Component extraProps={extraProps} {...props} />;};return newComponent;
};
上面这段代码实现了一个 WithHelloWorld 的高阶组件,功能就是为传入的组件添加一个 extraProps 参数,并且在使用时会自动打印 hello world。
高阶组件的作用
高阶组件的作用其实就是将一些通用的逻辑进行抽离,在实现新的组件时不需要重复实现已有逻辑。下面将将项目中使用的案例。
例如在一个表单中,如果每一个表单项都会有上面的 formLabel 作为字段名,下面的 helperText 帮助用户填写表单。
如果触发了表单验证,还需要再增加一个错误提示。
而除了这些每个表单项都有的东西,中间的表单组件却不尽相同,可能是输入框、选择器、单选框、多选框等等组件,因此需要有一定的拓展性。那么这时候我们就可以通过高阶组件实现将通用功能的抽离,并且能动态传入组件,任意进行拓展。
高阶组件的实现
基础框架
接下来我们尝试按照上面的分析实现一个高阶组件,高阶组件一般以 With + xxx 格式进行命名,这里我们将组件命名为 WithFormItem。以下的代码都是通过 ChakraUI + react hook form 进行实现的。
首先实现一个最小的高阶组件框架:
// WithFormItem.tsx
const WithFormItem = (Component) => {const FormItem= (props) => {return <Component {...props}></Component>}return FormItem
}
export default WithFormItem
结合 TS
解下来要考虑一下如何实现结合 ts 实现类型提示,这一步还是有些坑的,首先我们需要知道我们在调用高阶组件的时候,其实调用的是里面返回的 FormItem 组件,因此我们需要将类型从 WithFormItem 函数传到 FormItem 内的,这里可以使用泛型实现:
export type BaseFieldProps = {name: string;error: boolean;label?: string;toolTip?: JSX.Element | string;
};
export type DefaultProps = {name: string;label?: string;unit?: string;isOptional?: boolean;helperText?: string | React.ReactNode;labelTooltip?: string;isRequired?: boolean;max?: number;min?: number;maxLength?: number;minLength?: number;
};
const WithFormItem = <FieldType extends BaseFieldProps>( Component: React.FC<FieldType> ) => {const FormItem: React.FC<DefaultProps & Omit<FieldType, keyof BaseFieldProps>> = (props) => {const componentProps = { ...props } as FieldType;return <Component {...componentProps} />;};return FormItem;
};
export default WithFormItem;
咱们分析一下上面的代码,首先我们为 WithFormItem 函数定义了一个泛型 FieldType ,这个泛型代表的是使用高阶组件函数时,我们需要传入的 待处理组件 的参数类型,FieldType 继承了 BaseFieldProps,说明待处理的组件上至少得具有 BaseFieldProps 定义的这几个参数,否则是会提示的。
而 处理后的组件 的类型则比较复杂,DefaultProps 说明处理后的组件上至少得具有 DefaultProps 定义的这几个参数 ,而 DefaultProps & Omit<FieldType, keyof BaseFieldProps> 这一步操作则是将 将前面传入的泛型 FieldType 中的 BaseFieldProps 取差集,再与 DefaultProps 取并集,最终返回的组件类型为 DefaultProps + FieldType - BaseFieldProps。
举个使用的例子:
import WithFormItem, { BaseFieldProps } from './WithFormItem.tsx';
export type TextareaProps = BaseFieldProps & {customProp: "自定义参数" };
const Textarea: React.FC<TextareaProps> = ({ name, field, error, ...rest }) => (<ChakraTextarea w="100%" isInvalid={error} {...field} {...rest} />
);
export WithFormItem<TextareaProps>(Textarea)
当我们使用 Textarea 组件时,我们自定义的参数只有一个 customProp, 传入高阶组件就会替我们添加上 DefaultProps 中的所有参数,在使用组件时就可以获得完整的类型提示了。
组件使用
在上面的代码中,我并没有展示具体的逻辑实现过程代码,大家在类型理解后自行根据业务实现即可,下面讲讲我是怎么使用高阶组件的:
import React, { memo } from 'react';
const WithFormItem = <FieldType extends BaseFieldProps>( Component: React.FC<FieldType> ) => {const FormItem: React.FC<DefaultProps & Omit<FieldType, keyof BaseFieldProps>> = (props) => {const componentProps = { ...props } as FieldType;return <Component {...componentProps} />;};return memo(FormItem);
};
在返回处理后的组件时使用 memo 进行包裹,React.momo 其实也是一个高阶组件噢。
// FormItem/index.tsx
import WithFormItem from './WithFormItem';
import TextInput, { TextInputProps } from './TextInput';
import NumberInput, { NumberInputProps } from './NumberInput';
import Textarea, { TextareaProps } from './Textarea';
import Password, { PasswordProps } from './Password';
import Checkbox, { CheckboxProps } from './Checkbox';
import Select, { SelectProps } from './Select';
import MultiSelect, { MultiSelectProps } from './MultiSelect';
import LabelCreate, { LabelCreateProps } from './LabelCreate';
import CodeEditor, { CodeEditorProps } from './CodeEditor';
const FormItem = {Text: WithFormItem<TextInputProps>(TextInput),Number: WithFormItem<NumberInputProps>(NumberInput),Textarea: WithFormItem<TextareaProps>(Textarea),Checkbox: WithFormItem<CheckboxProps>(Checkbox),Select: WithFormItem<SelectProps>(Select),Password: WithFormItem<PasswordProps>(Password),MultiSelect: WithFormItem<MultiSelectProps>(MultiSelect),LabelCreate: WithFormItem<LabelCreateProps>(LabelCreate),CodeEditor: WithFormItem<CodeEditorProps>(CodeEditor),
};
export default FormItem;
然后将所有的表单组件在 index.tsx 中统一引入进行处理,再进行暴露一个 FormItem 对象,对象上的每一个属性对应一个组件。
在页面中引用的时候,就非常方便了,直接通过 . 语法获取组件,使用方式如下:
import FormItem from "components"
<FormItem.Textname="name"label="Name"isRequiredmaxLength={128}
/>
代码提示非常友好,而且根据你选择的组件不同,参数类型也会根据你所传入高阶组件的范型进行提示,避免参数误传或少传: 
注意点
在返回的组件中,我并没有直接将参数一个一个传入 Component 中,而是使用以下写法,
const componentProps = { ...props } as FieldType;
<Component {...componentProps} />
如果你直接将参数传入组件会得到这个报错:
<Component name={name} />
总结
这篇文章为大家介绍了高阶组件的概念及使用场景,以及如何配置高阶组件的类型,当然实现的方式与业务有部分关联,如果你有更好的实现方式不如在评论区指出。希望这篇文章可以为大家解决一些问题,如果觉得文章对你有帮助不妨点个赞👍
最后
整理了一套《前端大厂面试宝典》,包含了HTML、CSS、JavaScript、HTTP、TCP协议、浏览器、VUE、React、数据结构和算法,一共201道面试题,并对每个问题作出了回答和解析。

有需要的小伙伴,可以点击文末卡片领取这份文档,无偿分享
部分文档展示:




文章篇幅有限,后面的内容就不一一展示了
有需要的小伙伴,可以点下方卡片免费领取
本文介绍如何利用React Hooks和TypeScript创建一个具有完整类型提示的高阶组件(HOC)。内容涵盖高阶组件的概念、作用,以及结合TS实现类型提示的详细步骤,包括基础框架搭建、泛型应用和组件使用示例。通过高阶组件,可以复用组件逻辑,减少代码重复,并确保类型安全。
586

被折叠的 条评论
为什么被折叠?



