起因
最近在项目重构时,我注意到一个性能隐患。每次在使用 Input 组件时,我们都是这样写的:
<Input
onInput={(e) => {
const value = e.currentTarget.value;
// 处理值的逻辑
}}
/>
这种内联的箭头函数写法会导致每次渲染都创建新的回调函数,可能引起不必要的重渲染。于是我想把它提取出来用 useCallback 包裹。
遇到的问题
但在提取回调函数时,我遇到了类型问题:
// 这时候完全不知道 e 的具体类型
const handleInput = useCallback((e: ???) => {
const value = e.currentTarget.value;
// 处理值的逻辑
}, []);
寻找事件类型
为了获取到正确的事件类型,我使用了 ComponentProps:
import { ComponentProps } from 'react';
type InputProps = ComponentProps<'input'>;
// 或者从你使用的组件库中获取
// type InputProps = ComponentProps<typeof Input>;
const handleInput = useCallback((e: InputProps['onInput']) => {
const value = e.currentTarget.value;
// 处理值的逻辑
}, []);
进一步优化
在解决了类型问题后,我想到既然每次都要从事件对象里取值,不如封装一个新组件,直接传值给回调函数。
最初的想法是这样的:
interface CustomInputProps extends InputProps {
// 试图覆盖原来的 onInput 类型
onInput: (value: string) => void;
}
结果遇到了类型错误,痛,太痛了,我又想直接切换AnyScript模式了~
柳暗花明
正当一筹莫展时,我发现了 OverrideProps 这个工具类型。使用它重写后,代码变得非常优雅:
type OverrideProps<T, U> = Omit<T, keyof U> & U;
interface CustomInputProps extends OverrideProps<InputProps, {
onInput: (value: string) => void;
}> {}
const CustomInput: React.FC<CustomInputProps> = ({
onInput,
...props
}) => {
const handleInput = (e: React.FormEvent<HTMLInputElement>) => {
onInput(e.currentTarget.value);
};
return <Input {...props} onInput={handleInput} />;
};
更多用法
用着用着,我发现 OverrideProps 还能做更多事情:
- 修改多个属性:
type EnhancedInputProps = OverrideProps<InputProps, {
onInput: (value: string) => void;
onChange: (value: string) => void;
value: string; // 把可选属性改为必选
}>;
- 配合泛型使用:
type ValueInput<T> = OverrideProps<InputProps, {
value: T;
onChange: (value: T) => void;
}>;
// 可以派生出各种类型的输入框
type NumberInput = ValueInput<number>;
type DateInput = ValueInput<Date>;
- 扩展原有属性:
type PhoneInputProps = OverrideProps<InputProps, {
onInput: (value: string) => void;
format?: 'CN' | 'US'; // 添加新属性
}>;
实用技巧
- 保持类型收敛:
// 好的写法
type GoodProps = OverrideProps<BaseProps, {
onChange: (value: string) => void;
}>;
// 避免的写法
type BadProps = OverrideProps<BaseProps, {
onChange: any; // 失去类型保护
}>;
- 添加类型说明:
interface CustomProps extends OverrideProps<BaseProps, {
/** 值变化时的回调 */
onChange: (value: string) => void;
}> {}
小结
OverrideProps 解决了在组件二次封装时常见的类型覆盖问题。它既保留了原组件的类型定义,又能精确地修改我们需要改变的属性类型。
使用 OverrideProps 的几个好处:
- 代码更简洁,不用手动写一堆 Omit
- 类型提示更准确
- 维护更方便,想改什么属性直接加就行
有时候就是这样,从一个简单的性能优化开始,却意外发现了更优雅的编程方式。没有无脑AnyScript的感觉真好!