文章目录
- 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; // 可选,封装 label
wrapperClassName?: 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. 合理使用默认值
在封装中添加默认值可以提升使用体验:
<Select
showSearch
allowClear
placeholder="请选择"
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[];
}
并配合传入:
<Select
options={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. 使用示例
<CustomSelect
label="城市"
value={selectedCity}
onChange={setSelectedCity}
options={[
{ label: '北京', value: 'beijing' },
{ label: '上海', value: 'shanghai' }
]}
/>
…props 位置
{...props}
应该放在 最后面。
推荐顺序:最后
<Select
showSearch
allowClear
placeholder="请选择"
{...props}
/>
原因:
-
避免被覆盖
如果你把...props
放在前面,后面设置的属性会覆盖它,导致用户传入的属性失效。// 错误示例(可能导致用户传入的属性无效) <Select {...props} showSearch={false} // 会覆盖 props.showSearch,即使用户传了 true />
-
正确示例:用户可以覆盖默认行为
<Select showSearch allowClear placeholder="请选择" {...props} // 用户传入的值会覆盖上面的默认值 />
例如用户传入
allowClear={false}
,最终就会生效。
简要总结
放置位置 | 是否推荐 | 原因说明 |
---|---|---|
最后面 | ✅ 推荐 | 用户传入值可覆盖默认值 |
最前面 | ❌ 不推荐 | 默认值会覆盖用户传入值,造成困惑 |
如果你还封装了内部逻辑(比如 onChange
的包裹处理),那可以手动合并它,而不是直接写在默认值中。
合并内外部逻辑
如果你希望在自定义组件中:
内部自定义属性可以“加工”后使用
同时仍 兼容外部传进来的属性,即可以被用户覆写/增强
场景示例:处理 onChange
我们以 onChange
为例,如果你想在组件内部处理一些逻辑后再执行用户传入的 onChange
,做法如下:
方案一:手动合并函数或属性
const handleChange = (value: any, option: any) => {
// 自定义逻辑,比如打日志
console.log('[CustomSelect] selected:', value);
// 调用外部传入的 onChange(如果有)
props.onChange?.(value, option);
};
<Select
showSearch
allowClear
placeholder="请选择"
{...props}
onnChange={handleChange}
/>
方案二:兼顾默认值 + 用户自定义
例如:
<Select
showSearch={props.showSearch ?? true} // 外部可以传 false,否则默认 true
allowClear={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>}
<Select
showSearch={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);
};
<Select
showSearch
allowClear
placeholder="请选择"
onChange={handleChange}
// 明确列出其他需要的 props
style={props.style}
className={props.className}
// 其他需要的 props...
/>
即建议不要偷懒,直接使用 ...props
,而是使用什么传入什么。并且可以参考 onChagne
一样合并内外部逻辑。