【问题标题】:How to make this discriminated union work?如何让这个受歧视的工会发挥作用?
【发布时间】:2020-03-11 21:15:09
【问题描述】:

尝试创建一个简单的表格组件,但在按我想要的方式工作时遇到了一些麻烦。

TL;DR 如何在这个Playground Link 中写入Column3 的类型,以便消除2 个错误?

这行得通

所以给出以下常见的东西:

type Data = { id: number, name: string };
const data: Data[] = [
  { id: 1, name: 'Alice' },
  { id: 2, name: 'Bob' }
]

type Header = { header: string };

这是一个变体,可让您提供类型安全的字段名称,并可选择为该字段的值提供格式化函数:

type Value1<Row> = {
  [Key in keyof Row]: {
    field: Key;
    format?: (value: Row[Key]) => string;
  };
}[keyof Row];

type Column1<Row> = Header & Value1<Row>;

const makeTable = <R>(r: R[], c: Column1<R>[]) => {}
makeTable(data, [
  { header: 'Id', field: 'id' },
  { header: 'Name', field: 'name', format: value => value.toUpperCase() },
])

这里有一个变体,它让你只提供一个格式化函数,而不是一个值你得到行对象:

type Value2<Row> = {
  format: (row: Row) => string;
};

type Column2<Row> = Header & Value2<Row>;

const makeTable = <R>(r: R[], c: Column2<R>[]) => {};
makeTable(data, [
  { header: 'Id', format: row => row.id.toString() },
  { header: 'Name', format: row => row.name.toUpperCase() },
])

这不是...

当我想结合这两个时,问题就出现了,因为对于某些列,第一个变体有意义,而对于其他列,第二个变体有意义。

我想我可以这样做,但 Typescript 不高兴......

type Column3<Row> = Header & (Value1<Row> | Value2<Row>); // <-- Non-working discriminated union?

const makeTable = <R>(r: R[], c: Column3<R>[]) => { };
makeTable(data, [
  { header: 'Id', field: 'id' },
  { header: 'Name', field: 'name', format: value => value.toUpperCase() },
  { header: 'Test', format: row => `${row.id} : ${row.name}` },
])

显然data 的类型现在不正确,而且由于某种原因,第三列中的row 隐含为any,当然不应该这样。

我可以通过明确说明通用 Row 类型来“修复”data 的类型:

makeTable<Data>(data, [
  { header: 'Id', field: 'id' },
  { header: 'Name', field: 'name', format: value => value.toUpperCase() },
  { header: 'Test', format: row => `${row.id} : ${row.name}` },
])

但是row 仍然是隐含的any,而且这里真的没有必要显式,所以这里肯定有什么不妥……但是……我不明白这里发生了什么. ????

我在这里误会了什么?

【问题讨论】:

    标签: typescript


    【解决方案1】:

    Value1Value2 中定义的格式不明确
    编译器无法在 Row[Key] //Value1Row //Value2 之间分辨 row 的类型
    我建议你切换 Value1 和 Value2 的format

    type Value1<Row> = {
      [Key in keyof Row]: {
        field: Key;
        format_field?: (value: Row[Key]) => string;
      };
    }[keyof Row];
    
    type Value2<Row> = {
      format_row: (row: Row) => string;
    };
    
    (<R>(r: R[], c: Column3<R>[]) => {})<Data>(data, [
      { header: 'Id', field: 'id' },
      { header: 'Name', field: 'name', format_field: value => value.toUpperCase() },
      { header: 'Test', format_row: row => `${row.id} : ${row.name}` },
    ]) //No more ambigeous
    

    【讨论】:

    • 对,这实际上看起来效果很好!我认为 Typescript 能够将它们分开,并且我可以使用 if( 'field' in column ) 类型保护最终将它们分开,但是是的。 ??
    【解决方案2】:

    所以,让我们将 data 上的错误类型归为通用默认类型(因为 Row 从未定义为实际类型,我认为 Typescript 在某些时候将其默认为 string 以进行类型解析(我可能错了))。

    format 函数中 row 的类型为 any 是由于 Typescript not inferring types for function arguments。关于原因,该线程有很多很好的讨论。事实上,在讨论中的某一时刻,提到了

    如今,TypeScript 中没有任何东西可以从变量的使用情况中推断出有关变量的信息。

    关于可能产生的复杂性的简短示例,z.format 中的row 是什么类型?

    type t = { format: (row: string) => string } | { format: (row: number) => number }
    const z: t = {format: row => row }
    

    使用过程中无需额外注解:

    const z: t = {format: (row: number) => row}
    

    编译器无法轻易判断row 应该是什么形状。不幸的是,这迫使我们有时必须要明确表达。

    要“修复”Column3 的类型,您只需在使用时更加明确:

    type Column3<Row> = Header & (Value1<Row> | Value2<Row>);
    type R = Data
    (<R>(r: R[], c: Column3<R>[]) => {})(data, [
      { header: 'Id', field: 'id' },
      { header: 'Name', field: 'name', format: value => value.toUpperCase() },
      { header: 'Test', format: (row: R) => `${row.id} : ${row.name}` },
    ])
    

    【讨论】:

    • 很奇怪,但我想这是有道理的。我不明白为什么data 最终成为string。就像,这是从哪里来的? ? 如果我在 format 函数中指定 Row 的类型,那么它突然也会为data 获得正确的类型,但如果我只使用field 变体,那么它会迫使我显式指定泛型类型以获得对的。 ?
    • 是的,我也注意到了这一点,我同意这很奇怪。这可能是类型解析的错误或工件达到基本情况?身份证
    猜你喜欢
    • 1970-01-01
    • 2011-11-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-08
    • 1970-01-01
    • 2017-05-14
    • 2023-02-19
    相关资源
    最近更新 更多