HOW - 基于组件库组件改造成自定义组件基本规范
文章目录
- Select 选择器改造
- 1. 明确组件目标
- 2. 定义组件 API
- 3. 合理使用默认值
- 4. 支持类型安全的 options 传递
- 5. 支持 ForwardRef(可选)
- 6. 封装样式(可选)
- 7. 使用示例
- ...props 位置
- 推荐顺序:最后
- 原因:
- 简要总结
- 合并内外部逻辑
- 场景示例:处理 onChange
- 方案一:手动合并函数或属性
- 方案二:兼顾默认值 + 用户自定义
- 总结:封装时处理逻辑建议
- 最佳实践示例组件
- 更安全的方式
Select 选择器改造
比如,要将 Ant Design 的 Select
组件封装成你的 CustomSelect
组件并规范化封装,建议遵循以下几个方面的最佳实践:
1. 明确组件目标
你可以定义这个 CustomSelect
的目标,例如:
- 更统一的样式
- 集成默认功能(比如加载状态、本地搜索等)
- 简化使用(比如默认占位符、默认事件处理)
2. 定义组件 API
设计你自己的 CustomSelect
组件的 props 类型时,建议基于 antd 的 SelectProps
做扩展:
// CustomSelect.tsx
import React from 'react';
import { Select, SelectProps } from 'antd';export interface CustomSelectProps extends SelectProps {label?: string; // 可选,封装 labelwrapperClassName?: string; // 可选,外层 class
}const CustomSelect: React.FC<CustomSelectProps> = ({label,wrapperClassName,...props
}) => {return (<div className={wrapperClassName}>{label && <label className="block mb-1 text-gray-700">{label}</label>}<Select {...props} /></div>);
};export default CustomSelect;
3. 合理使用默认值
在封装中添加默认值可以提升使用体验:
<SelectshowSearchallowClearplaceholder="请选择"optionFilterProp="label"filterOption={(input, option) =>(option?.label ?? '').toLowerCase().includes(input.toLowerCase())}{...props}
/>
你可以在 CustomSelect
内设置 showSearch
、allowClear
、filterOption
等默认行为。
4. 支持类型安全的 options 传递
你可以加一个 options
支持(类型为 DefaultOptionType[]
):
import type { DefaultOptionType } from 'antd/es/select';interface CustomSelectProps extends SelectProps {options?: DefaultOptionType[];
}
并配合传入:
<Selectoptions={props.options}{...otherProps}
/>
5. 支持 ForwardRef(可选)
如果你希望外部能访问内部 ref
,可以使用 forwardRef
:
const CustomSelect = React.forwardRef<any, CustomSelectProps>((props, ref) => {return <Select ref={ref} {...props} />;
});
6. 封装样式(可选)
统一样式可以放在 CustomSelect.module.css
或用 tailwind
写死样式:
<Select className="w-full rounded-md" />
7. 使用示例
<CustomSelectlabel="城市"value={selectedCity}onChange={setSelectedCity}options={[{ label: '北京', value: 'beijing' },{ label: '上海', value: 'shanghai' }]}
/>
…props 位置
{...props}
应该放在 最后面。
推荐顺序:最后
<SelectshowSearchallowClearplaceholder="请选择"{...props}
/>
原因:
-
避免被覆盖
如果你把...props
放在前面,后面设置的属性会覆盖它,导致用户传入的属性失效。// 错误示例(可能导致用户传入的属性无效) <Select{...props}showSearch={false} // 会覆盖 props.showSearch,即使用户传了 true />
-
正确示例:用户可以覆盖默认行为
<SelectshowSearchallowClearplaceholder="请选择"{...props} // 用户传入的值会覆盖上面的默认值 />
例如用户传入
allowClear={false}
,最终就会生效。
简要总结
放置位置 | 是否推荐 | 原因说明 |
---|---|---|
最后面 | ✅ 推荐 | 用户传入值可覆盖默认值 |
最前面 | ❌ 不推荐 | 默认值会覆盖用户传入值,造成困惑 |
如果你还封装了内部逻辑(比如 onChange
的包裹处理),那可以手动合并它,而不是直接写在默认值中。
合并内外部逻辑
如果你希望在自定义组件中:
内部自定义属性可以“加工”后使用
同时仍 兼容外部传进来的属性,即可以被用户覆写/增强
场景示例:处理 onChange
我们以 onChange
为例,如果你想在组件内部处理一些逻辑后再执行用户传入的 onChange
,做法如下:
方案一:手动合并函数或属性
const handleChange = (value: any, option: any) => {// 自定义逻辑,比如打日志console.log('[CustomSelect] selected:', value);// 调用外部传入的 onChange(如果有)props.onChange?.(value, option);
};<SelectshowSearchallowClearplaceholder="请选择"{...props}onnChange={handleChange}
/>
方案二:兼顾默认值 + 用户自定义
例如:
<SelectshowSearch={props.showSearch ?? true} // 外部可以传 false,否则默认 trueallowClear={props.allowClear ?? true}placeholder={props.placeholder ?? '请选择'}{...props}
/>
这样可以兼顾默认值 + 用户自定义。
总结:封装时处理逻辑建议
类型 | 写法 | 是否允许外部覆盖 |
---|---|---|
函数型属性 | 主动调用外部函数(如 onChange) | ✅ 推荐 |
布尔/字符串属性 | 使用 ?? 提供默认值 | ✅ 推荐 |
最佳实践示例组件
export const CustomSelect: React.FC<CustomSelectProps> = ({label,wrapperClassName,onChange,...props
}) => {const handleChange = (value: any, option: any) => {// 内部逻辑console.log('[CustomSelect] selected:', value);// 外部逻辑onChange?.(value, option);};return (<div className={wrapperClassName}>{label && <label className="block mb-1 text-gray-700">{label}</label>}<SelectshowSearch={props.showSearch ?? true}allowClear={props.allowClear ?? true}placeholder={props.placeholder ?? '请选择'}{...props}onChange={handleChange}/></div>);
};
更安全的方式
更安全的方式是明确列出你需要的 props,而不是使用 {...props}
:
const handleChange = (value: any, option: any) => {// 内部逻辑console.log('[CustomSelect] selected:', value);// 外部逻辑onChange?.(value, option);};<SelectshowSearchallowClearplaceholder="请选择"onChange={handleChange}// 明确列出其他需要的 propsstyle={props.style}className={props.className}// 其他需要的 props...
/>
即建议不要偷懒,直接使用 ...props
,而是使用什么传入什么。并且可以参考 onChagne
一样合并内外部逻辑。