一次性能优化引发的 OverrideProps 探索

167 阅读2分钟

起因

最近在项目重构时,我注意到一个性能隐患。每次在使用 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 还能做更多事情:

  1. 修改多个属性:
type EnhancedInputProps = OverrideProps<InputProps, {
  onInput: (value: string) => void;
  onChange: (value: string) => void;
  value: string;  // 把可选属性改为必选
}>;
  1. 配合泛型使用:
type ValueInput<T> = OverrideProps<InputProps, {
  value: T;
  onChange: (value: T) => void;
}>;

// 可以派生出各种类型的输入框
type NumberInput = ValueInput<number>;
type DateInput = ValueInput<Date>;
  1. 扩展原有属性:
type PhoneInputProps = OverrideProps<InputProps, {
  onInput: (value: string) => void;
  format?: 'CN' | 'US';  // 添加新属性
}>;

实用技巧

  1. 保持类型收敛:
// 好的写法
type GoodProps = OverrideProps<BaseProps, {
  onChange: (value: string) => void;
}>;

// 避免的写法
type BadProps = OverrideProps<BaseProps, {
  onChange: any;  // 失去类型保护
}>;
  1. 添加类型说明:
interface CustomProps extends OverrideProps<BaseProps, {
  /** 值变化时的回调 */
  onChange: (value: string) => void;
}> {}

小结

OverrideProps 解决了在组件二次封装时常见的类型覆盖问题。它既保留了原组件的类型定义,又能精确地修改我们需要改变的属性类型。

使用 OverrideProps 的几个好处:

  1. 代码更简洁,不用手动写一堆 Omit
  2. 类型提示更准确
  3. 维护更方便,想改什么属性直接加就行

有时候就是这样,从一个简单的性能优化开始,却意外发现了更优雅的编程方式。没有无脑AnyScript的感觉真好!