React如何对组件封装,思路及操作是什么
Admin 2022-08-06 群英技术资讯 453 次浏览
由于考虑组件拆分得比较细,层级比较多,为了方便使用了
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进行举报,并提供相关证据,查实之后,将立刻删除涉嫌侵权内容。
猜你喜欢
方法:1、利用“$(input元素)”语句获取所有的input元素对象;2、利用prop()方法和disabled属性让获取到的input元素失效即可,语法为“input元素对象.prop("disabled","disabled");”。
这篇文章主要为大家介绍了前端框架封装Vue第三方组件的三个技巧示例详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
目录一、写在前面二、正文部分2.1 扁平数据转为 tree 数据2.2 tree 数据转为扁平数据2.3 完整测试 demo三、写在后面一、写在前面有时我们拿到的数据的数据结构可能不是理想的,那么此时就要求前端程序员,具有改造数据的能力。例如拿到扁平的数据, 但我们要应用在 tree 树形组件或 Cascader 级联
我们了解到,Node采用了事件驱动机制,而EventEmitter就是Node实现事件驱动的基础,本文主要介绍了node.js自定义实现EventEmitter,感兴趣的可以了解一下
这篇文章小编给大家分享的是JSBridge的内容,下文介绍了JSBridge是什么,JSBridge的原理和JSBridge的使用,文中示例介绍的很详细,感兴趣的朋友可以了解看看,下面让我们一起来学习一下吧!
成为群英会员,开启智能安全云计算之旅
立即注册Copyright © QY Network Company Ltd. All Rights Reserved. 2003-2020 群英 版权所有
增值电信经营许可证 : B1.B2-20140078 粤ICP备09006778号 域名注册商资质 粤 D3.1-20240008