从键列表推断TypeScript对象属性

piwo6bdm  于 2023-02-05  发布在  TypeScript
关注(0)|答案(1)|浏览(106)

👋
我正在编写一个CSV阅读器程序,该程序将一个头列表作为一个属性(我们将其命名为CsvHeaders。另一个属性返回一个函数,该函数的参数是一个对象,其键肯定是CsvHeaders的元素。

type CsvHeaders = readonly string[];
type CsvRow = Record<CsvHeaders[number], string>;

interface ICsvReaderProps {
  headers: CsvHeaders;
  onRowClick: (row: CsvRow) => void;
}

const a: ICsvReaderProps = {
  headers: ['A', 'B', 'C'] as const,
  onRowClick: (row) => console.log(row.Anything), // I want this to throw a type error since `Anything` !== `'A' | 'B' | 'C'`.
};

onRowClick中的row被推断为{ [x: string]: string },如何修改才能将x推断为CsvHeaders的元素?我不知道我哪里出错了,难道这在TS中还不可能吗?提前感谢!

6rqinv9w

6rqinv9w1#

为了使其正常工作,在与headers属性的元素相对应的字符串文本类型的并集中,ICsvReaderProps必须是generic:让我们调用类型参数T

interface ICsvReaderProps<T extends string> {
  headers: readonly T[];
  onRowClick: (row: Record<T, string>) => void;
}

那么onRowClick回调的row参数的类型为Record<T, string>(使用Record<K, V>实用程序类型来表示具有类型K键和类型V值的对象类型)。
因此,示例对象的类型为ICsvReaderProps<"A" | "B" | "C">,为了避免手动指定该类型参数(这将使您编写"A""B""C"两次),我们可以定义一个通用标识辅助函数:

const asCsvReaderProps = <T extends string>(x: ICsvReaderProps<T>) => x;

然后把const a: ICsvReaderProps = {...};写成const a = asCsvReaderProps({...});,我们试试看:

const a = asCsvReaderProps({
  headers: ['A', 'B', 'C'],
  onRowClick: (row) => console.log(row.Anything), // error!
  //                          -------> ~~~~~~~~
  // Property 'Anything' does not exist on type 
  // 'Record<"A" | "B" | "C", string>'.
})

看起来不错。编译器根据headers属性推断T"A" | "B" | "C",因此在onRowClick回调中上下文类型rowRecord<"A" | "B" | "C", string>。因此row已知具有AB、和string类型的C属性。它不知道是否具有Anything属性,因此根据需要出现编译器错误。
Playground代码链接

相关问题