React如何对组件封装,思路及操作是什么
Admin 2022-08-06 群英技术资讯 1406 次浏览
今天这篇我们来学习和了解“React如何对组件封装,思路及操作是什么”,下文的讲解详细,步骤过程清晰,对大家进一步学习和理解“React如何对组件封装,思路及操作是什么”有一定的帮助。有这方面学习需要的朋友就继续往下看吧!
由于考虑组件拆分得比较细,层级比较多,为了方便使用了
React.createContext + useContext作为参数向下传递的方式。
首先需要知道antd的Popover组件是继承自Tooltip组件的,而我们的CustomSelect组件是继承自Popover组件的。对于这种基于某个组件的二次封装,其props类型一般有两种方式处理: 继承, 合并。
interface IProps extends XXX;
type IProps = Omit<TooltipProps, 'overlay'> & {...};
对于Popover有个很重要的触发类型: trigger,默认有四种"hover" "focus" "click" "contextMenu", 并且可以使用数组设置多个触发行为。但是我们的需求只需要"hover"和"click", 所以需要对该字段进行覆盖。
对于Select, Checkbox这种表单控件来说,对齐二次封装,很多时候需要进行采用'受控组件'的方案,通过'value' + 'onChange'的方式"接管"其数据的输入和输出。并且value不是必传的,使用组件时可以单纯的只获取操作的数据,传入value更多是做的一个初始值。而onChange是数据的唯一出口,我觉得应该是必传的,不然你怎么获取的到操作的数据呢?对吧。
有一个注意点: 既然表单控件时单选框,复选框, 那我们的输入一边是string, 一边是string[],既大大增加了编码的复杂度,也增加了使用的心智成本。所以我这里的想法是统一使用string[], 而再单选的交互就是用value[0]等方式完成单选值与数组的转换。
// types.ts
import type { TooltipProps } from 'antd';
interface OptItem {
id: string;
name: string;
disabled: boolean; // 是否不可选
children?: OptItem[]; // 递归嵌套
}
// 组件调用的props传参
export type IProps = Omit<TooltipProps, 'overlay' | 'trigger'> & {
/** 选项类型: 单选, 复选 */
type: 'radio' | 'checkbox';
/** 选项列表 */
options: OptItem[];
/** 展示文本 */
placeholder?: string;
/** 触发行为 */
trigger?: 'click' | 'hover';
/** 受控组件: value + onChange 组合 */
value?: string[];
onChange?: (v: string[]) => void;
/** 样式间隔 */
size?: number;
}
import type { Dispatch, MutableRefObj, SetStateAction } from 'react';
import { createContext } from 'react';
import type { IProps } from './types';
export const Ctx = createContext<{
options: IProps['options'];
size?: number;
type: IProps['type'];
onChange?: IProps['onChange'];
value?: IProps['value'];
// 这里有两个额外的状态: shadowValue表示内部的数据状态
shadowValue: string[];
setShadowValue?: Dispatch<SetStateAction<string[]>>;
// 操作弹出框
setVisible?: (value: boolean) => void;
// 复选框的引用, 暴露内部的reset方法
checkboxRef?: MutableRefObject<{
reset: () => void;
} | null>;
}>({ options: [], shadowValue: [], type: 'radio' });
// index.tsx
/**
* 自定义下拉选择框, 包括单选, 多选。
*/
import { FilterOutlined } from '@ant-design/icons';
import { useBoolean } from 'ahooks';
import { Popover } from 'antd';
import classnames from 'classnames';
import { cloneDeep } from 'lodash';
import type { FC, ReactElement } from 'react';
import { memo, useEffect, useRef, useState } from 'react';
import { Ctx } from './config';
import Controls from './Controls';
import DispatchRender from './DispatchRender';
import Styles from './index.less';
import type { IProps } from './types';
const Index: FC<IProps> = ({
type,
options,
placeholder = '筛选文本',
trigger = 'click',
value,
onChange,
size = 6,
style,
className,
...rest
}): ReactElement => {
// 弹窗显示控制(受控组件)
const [visible, { set: setVisible }] = useBoolean(false);
// checkbox专用, 用于获取暴露的reset方法
const checkboxRef = useRef<{ reset: () => void } | null>(null);
// 内部维护的value, 不对外暴露. 统一为数组形式
const [shadowValue, setShadowValue] = useState<string[]>([]);
// value同步到中间状态
useEffect(() => {
if (value && value?.length) {
setShadowValue(cloneDeep(value));
} else {
setShadowValue([]);
}
}, [value]);
return (
<Ctx.Provider
value={{
options,
shadowValue,
setShadowValue,
onChange,
setVisible,
value,
size,
type,
checkboxRef,
}}
>
<Popover
visible={visible}
onVisibleChange={(vis) => {
setVisible(vis);
// 这里是理解难点: 如果通过点击空白处关闭了弹出框, 而不是点击确定关闭, 需要额外触发onChange, 更新数据。
if (vis === false && onChange) {
onChange(shadowValue);
}
}}
placement="bottom"
trigger={trigger}
content={
<div className={Styles.content}>
{/* 分发自定义的子组件内容 */}
<DispatchRender type={type} />
{/* 控制行 */}
<Controls />
</div>
}
{...rest}
>
<span className={classnames(Styles.popoverClass, className)} style={style}>
{placeholder ?? '筛选文本'}
<FilterOutlined style={{ marginTop: 4, marginLeft: 3 }} />
</span>
</Popover>
</Ctx.Provider>
);
};
const CustomSelect = memo(Index);
export { CustomSelect };
export type { IProps };
/** 控制按钮行: "重置", "确定" */
import { Button } from 'antd';
import { cloneDeep } from 'lodash';
import type { FC } from 'react';
import { useContext } from 'react';
import { Ctx } from './config';
import Styles from './index.less';
const Index: FC = () => {
const { onChange, shadowValue, setShadowValue, checkboxRef, setVisible, value, type } =
useContext(Ctx);
return (
<div className={Styles.btnsLine}>
<Button
type="primary"
ghost
size="small"
onClick={() => {
// radio: 直接重置为value
if (type === 'radio') {
if (value && value?.length) {
setShadowValue?.(cloneDeep(value));
} else {
setShadowValue?.([]);
}
}
// checkbox: 因为还需要处理全选, 需要交给内部处理
if (type === 'checkbox') {
checkboxRef?.current?.reset();
}
}}
>
重置
</Button>
<Button
type="primary"
size="small"
onClick={() => {
if (onChange) {
onChange(shadowValue); // 点击确定才触发onChange事件, 暴露内部数据给外层组件
}
setVisible?.(false); // 关闭弹窗
}}
>
确定
</Button>
</div>
);
};
export default Index;
/** 分发详情的组件,保留其可拓展性 */
import type { FC, ReactElement } from 'react';
import CheckboxRender from './CheckboxRender';
import RadioRender from './RadioRender';
import type { IProps } from './types';
const Index: FC<{ type: IProps['type'] }> = ({ type }): ReactElement => {
let res: ReactElement = <></>;
switch (type) {
case 'radio':
res = <RadioRender />;
break;
case 'checkbox':
res = <CheckboxRender />;
break;
default:
// never作用于分支的完整性检查
((t) => {
throw new Error(`Unexpected type: ${t}!`);
})(type);
}
return res;
};
export default Index;
import { Radio, Space } from 'antd';
import type { FC, ReactElement } from 'react';
import { memo, useContext } from 'react';
import { Ctx } from './config';
const Index: FC = (): ReactElement => {
const { size, options, shadowValue, setShadowValue } = useContext(Ctx);
return (
<Radio.Group
value={shadowValue?.[0]} // Radio 接受单个数据
onChange={({ target }) => {
// 更新数据
if (target.value) {
setShadowValue?.([target.value]);
} else {
setShadowValue?.([]);
}
}}
>
<Space direction="vertical" size={size ?? 6}>
{options?.map((item) => (
<Radio key={item.id} value={item.id}>
{item.name}
</Radio>
))}
</Space>
</Radio.Group>
);
};
export default memo(Index);
个人总结
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:mmqy2019@163.com进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
TypeScript中命名空间与模块化详情 目录 一.模块 二.命名空间 三.区别 一.模块 TypeScript 与ECMAScript 2015 一样,任何包含顶级 import 或者 export 的文件都被当成一个模块 相反地,如果一个文件不带有顶级的import或者export声明,那么它的内容被视为全局可见的 例如我们在在一个 TypeScript 工程下建立一个文件 1.ts,声明一个变量a,如下: const a = 1 然后在另一个文件同样声明一个变量a,这时候会出现错误信息 提示重复声明a变量,但是所处的空间是全局的
目录在data里引入相对路径问题解决如何在data中正常引入图片路径此时有两种解决方法在data里引入相对路径问题在项目的HTML中引入图片的相对路径,这样写是能找到图片显示出来的:img src=../../../static/img/step-ongoing.png但图片太多感觉太乱了了,想在data中通过变量统一
本篇文章给大家带来了关于javascript的相关知识,主要介绍了JavaScript的起源与发展,JavaScript作为赋予网页生命的前端基础技术,它可以实现相应的效果和交互,是前端开发不可或缺的基本配置之一,下面一起来了解一下JavaScript的前世今生,希望对大家有帮助。
本文主要介绍Map的相关内容,一些朋友对于JS中怎样用及什么时候用map替代JS对象,这些不是很理解,对此这篇就给大家来讲讲Map,感兴趣的朋友可以参考了解看看。
事实上,前端很少涉及对二进制数据的处理,但即便如此,我们偶尔总能在角落里看见它们的身影。 今天我们就来聊一聊前端的二进制家族:Blob、ArrayBuffer和Buffer
成为群英会员,开启智能安全云计算之旅
立即注册关注或联系群英网络
7x24小时售前:400-678-4567
7x24小时售后:0668-2555666
24小时QQ客服
群英微信公众号
CNNIC域名投诉举报处理平台
服务电话:010-58813000
服务邮箱:service@cnnic.cn
投诉与建议:0668-2555555
Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 ICP核准(ICP备案)粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008