You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

TypeScript泛型数组无法赋值给联合类型泛型数组的原因

类型不兼容的核心原因

你的直觉不成立的根源是泛型的协变/逆变规则,Column<T> 本身不是协变泛型,因此无法顺着 Adult extends Adult | Child 的关系推导出 Column<Adult> extends Column<Adult | Child>。

先理清类型兼容的基础规则

TS 里泛型的兼容方向完全由泛型参数的使用位置决定:

  • 如果泛型参数只出现在输出位置(比如函数返回值、只读属性),那这个泛型是协变的:比如type ReadonlyBox<T> = { value: T },只要A extends B,就有ReadonlyBox<A> extends ReadonlyBox<B>,这就是你直觉里的子集关系。
  • 如果泛型参数只出现在输入位置(比如函数参数),那这个泛型是逆变的:比如type Setter<T> = (v: T) => void,兼容方向是反过来的——只有B extends A,才有Setter<A> extends Setter<B>。
  • 如果泛型参数同时出现在输入和输出位置,那这个泛型是不变的,只有T完全相等时,两个泛型类型才兼容。

回到react-table的Column类型

Column<T> 里存在大量把T作为函数参数的属性定义,比如行渲染的Cell、取值的accessor、排序的sortType,这类属性的类型大致是(row: T, ...args: any[]) => any,T出现在逆变的输入位置,因此Column<T>是不变泛型:

  • Column<Adult> 只能赋值给Column<Adult>,不能赋值给Column<Adult | Child>
  • Column<Child> 只能赋值给Column<Child>,也不能赋值给Column<Adult | Child>

TS 做这个限制是完全合理的,举个会触发运行时错误的反例:

假设你在Column<Adult>里定义了一个sortType排序函数,逻辑是读取Adult独有的annualIncome(年收入)字段排序。如果TS允许你把这个列配置传给接收Column<Adult | Child>的表格,当表格渲染Child类型的行时,排序函数拿到Child对象读取annualIncome会得到undefined,直接导致排序逻辑崩溃。

你现在的联合类型readonly Column<Adult>[] | readonly Column<Child>[],语义是「这要么是一整套成人列配置,要么是一整套儿童列配置」;而useTable期望的readonly Column<Adult | Child>[]语义是「每一列都能同时处理成人和儿童两种数据」,两者语义本身就存在差异,不兼容是正常的。

可行的修复方案
  • 最安全的方案:类型窄化后分分支调用useTable
    通过类型守卫判断persons的元素类型,把代码分成Adult和Child两个分支,每个分支里columns和data的类型完全匹配,不会有类型错误,也不会有运行时风险:
    const isAdult = (p: Adult | Child): p is Adult => 'annualIncome' in p // 用Adult独有的字段做类型判断
    if (persons.every(isAdult)) {
      // 此分支内persons是Adult[],columns是Column<Adult>[]
      const tableProps = useTable<Adult>({ columns, data: persons }, useSortBy)
      // 渲染表格
    } else {
      // 此分支内persons是Child[],columns是Column<Child>[]
      const tableProps = useTable<Child>({ columns, data: persons }, useSortBy)
      // 渲染表格
    }
    
  • 次安全方案:统一列配置的类型约束
    如果你的表格列确实只会用到Adult和Child共有的字段,不会访问任何独有属性,可以直接把PersonTablePropData里的columns类型定义为readonly Column<Adult | Child>[],从源头统一类型,写列配置的时候TS会自动校验,阻止你访问类型独有的字段。
  • 快速方案:类型断言(不推荐)
    如果你完全确定运行时不会出问题,可以直接用类型断言绕过TS检查:
    useTable<Adult|Child>({ columns: columns as readonly Column<Adult|Child>[], data: persons }, useSortBy)
    
    这个方案会丢失类型安全,后续如果有人在列配置里加了访问独有字段的逻辑,TS不会提示,很容易出线上bug。

内容的提问来源于stack exchange,提问作者Adrian Bielefeldt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 14:09:26