React 组件中的条件 TypeScript 泛型

发布: (2026年1月9日 GMT+8 22:01)
7 min read
原文: Dev.to

Source: Dev.to

在我之前的文章这里中,我承诺会继续讨论 TypeScript 泛型——这是我在使用 React 组件时认为尤为重要的话题。

在那篇文章里,我们介绍了泛型组件的基本定义,并看了一个关于何时以及为何使用泛型的简单示例。在本篇文章中,我们将迈出下一步,重点关注条件泛型以及它们在真实 React 组件中的应用。

这里,我想展示一个我在实际项目中使用的例子,它让我深刻体会到泛型类型的优雅与强大。

Source:

问题:没有泛型的选择组件

让我们考虑一个选择组件,它可以根据属性选择一个或多个选项。

// Just an interface of option structure
interface Option {
  id: number;
  name: string;
}

// It's your value type
type Value = Option[] | Option | undefined;

// Component props interface
interface Props {
  value?: Value;
  options?: Option[];
  multiple?: boolean;
  onChange?: (value: Value) => void;
}

在使用该组件时,你 并不完全安全,因为 TypeScript 并不知道组件是处于多选模式还是单选模式。
value?: ValueonChange?: (value: Value) => void 在两种状态下始终可用,且不受 multiple?: boolean 的影响。这并不够严格——我们希望得到以下保证:

  • 如果 multiplefalse(或未提供) → value 必须是 单个对象(或 undefined);
  • 如果 multipletruevalue 必须是 数组

Props 接口引入泛型

我们保持相同的 Option 接口:

interface Option {
  id: number;
  name: string;
}

现在我们为 Props 增加两个泛型参数 OM

interface Props<O extends Option = Option, M extends boolean | undefined = undefined> {
  value?: M extends undefined | false ? O | undefined : O[];
  options?: O[];
  multiple?: M;
  onChange?: (
    values: M extends undefined | false ? O | undefined : O[]
  ) => void;
}

说明

泛型含义
O扩展 Option;表示选项类型。
M扩展 `boolean
valueM extends undefined | false ? O | undefined : O[] – 当 multiplefalse 或未提供时为单个 O(或 undefined),否则为数组 O[]
onChange回调参数使用相同的条件类型。

提取条件类型

重复的条件表达式会显得冗余,因此我们将其提取为独立的泛型类型:

type Value<O extends Option, M extends boolean | undefined> =
  M extends undefined | false ? O | undefined : O[];

现在 Props 变得更加简洁:

interface Props<O extends Option = Option, M extends boolean | undefined = undefined> {
  value?: Value<O, M>;
  options?: O[];
  multiple?: M;
  onChange?: (values: Value<O, M>) => void;
}

Source:

为组件本身添加泛型

组件本身也必须是泛型的,并将这些参数传递给 Props

export const SelectionExample = <
  O extends Option = Option,
  M extends boolean | undefined = undefined
>({
  value = undefined,
  multiple = false,
  onChange = () => undefined,
  options = [],
}: Props<O, M>) => {
  const isOptionSelected = (option: O): boolean => {
    // Implementation using `value` and `multiple`
    return false;
  };

  const handleOptionClick = (option: O) => {
    if (multiple) {
      // Implementation for multiple selection using `onChange`
    } else {
      // Implementation for single selection using `onChange`
    }
  };

  return (
    <div>
      {options.map((option) => {
        const selected = isOptionSelected(option);
        return (
          <div key={option.id}>
            {/* Render option UI */}
          </div>
        );
      })}
    </div>
  );
};

注意: 泛型参数 <O, M> 在组件上声明,然后传递给 Props

使用通用组件

1️⃣ 多选

export const UsageExample = () => {
  const options: Option[] = [
    { id: 1, name: "First" },
    { id: 2, name: "Second" },
    { id: 3, name: "Third" },
  ];

  return (
    <SelectionExample
      multiple
      options={options}
      onChange={(selected) => {
        // `selected` is inferred as `Option[]`
        console.log("selected:", selected);
      }}
    />
  );
};

2️⃣ 单选

export const SingleUsage = () => {
  const options: Option[] = [
    { id: 1, name: "First" },
    { id: 2, name: "Second" },
    { id: 3, name: "Third" },
  ];

  return (
    <SelectionExample
      options={options}
      onChange={(selected) => {
        // `selected` is inferred as `Option | undefined`
        console.log("selected:", selected);
      }}
    />
  );
};

在这两种情况下,TypeScript 能够正确推断 value 的结构以及 onChange 的参数类型,从而为我们提供了想要的严格保证。

要点

  • 条件泛型 让我们能够表达属性之间的关系(例如 multiplevalue 的形状)。
  • 将重复的条件逻辑提取为可复用的泛型类型(Value)可以保持代码 DRY 且易读。
  • 在组件本身上声明泛型并将其转发到 props 接口,可确保整个组件的类型安全。

使用这种模式,你可以构建高度可复用、类型安全的 React 组件,并根据 props 的取值自动调整其 API。祝编码愉快!

多选(附加示例)

export const UsageExample = () => {
  return (
    <>
      {/* ... */}
      {/* `values` is the selected options array */}
      {console.log(values)}
    </>
  );
};

单选(附加示例)

export const UsageExample = () => {
  return (
    <>
      {/* ... */}
      {/* `value` is the selected option */}
      {console.log(value)}
    </>
  );
};

错误用法

export const UsageExample = () => {
  return (
    <>
      {/* ... */}
      {/* `values` has type `Option | undefined` – this is a misuse when multiple is expected */}
      {console.log(values)}
    </>
  );
};

我知道这篇文章可能会促使你更深入地探索 TypeScript 的特性。不过,我相信当你完成自己的类似实现并意识到组件变得更加强大时,你会感到非常满足。

Back to Blog

相关文章

阅读更多 »

React 组件中的 TypeScript 泛型

介绍:泛型并不是在 React 组件中每天都会使用的东西,但在某些情况下,它们可以让你编写既灵活又类型安全的组件。