类型收缩是否可能影响TypeScript函数中的泛型类型参数?

kpbpu008  于 2023-03-09  发布在  TypeScript
关注(0)|答案(1)|浏览(116)

是否可以让TypeScript正确推断funcAorB函数中callback参数的类型?当调用funcAorB时,它可以从调用者的Angular 推断callback参数的类型,但不能在funcAorB的主体中推断。我希望既然TypeScript能够确定data参数的正确类型,由于switch语句测试key成员,因此无论在函数中使用T泛型类型,它都将使用相同的类型。
我喜欢在调用funcAorB时正确地推断callback的类型,我只是希望我能在funcAorB中得到同样的推断。
打字机游戏场

type DataA = {
  readonly key: 'a',
};

type DataB = {
  readonly key: 'b',
};

function funcA(data: DataA, callback: (data: DataA) => void) {}
function funcB(data: DataB, callback: (data: DataB) => void) {}

function funcAorB<T extends DataA | DataB>(data: T, callback: (data: T) => void) {
  switch (data.key) {
    case 'a': {
      // TypeScript believes that data is of type DataA now.
      // But, I have to use "as" because TypeScript thinks that
      // callback can be ((data: DataA) => void | (data: DataB) => void).
      funcA(data, callback as (data: DataA) => void);
      break;
    }
    case 'b': {
      // TypeScript believes that data is of type DataB now.
      // But, I have to use "as" because TypeScript thinks that
      // callback can be ((data: DataA) => void | (data: DataB) => void).
      funcB(data, callback as (data: DataB) => void);
      break;
    }
  }
}

// The callback type is inferred properly at the call site.
funcAorB({ key: 'a' }, (data) => {
  // This succeeds because data is inferred to be DataA.
  const a: 'a' = data.key;
});

// The callback type is inferred properly at the call site.
funcAorB({ key: 'b' }, (data) => {
  // This succeeds because data is inferred to be DataB.
  const b: 'b' = data.key;
});
ffx8fchx

ffx8fchx1#

TypeScript目前无法使用控制流分析来缩小或重新约束generic类型参数。它可以缩小泛型类型的 value 的表观类型,但泛型类型参数本身将顽固地保持不变。因此data被缩小为DataADataB,但T extends DataA | DataB不变。
其中一个原因是TypeScript的类型系统目前无法表达T需要发生什么。

const dA: DataA = { key: "a" };
const dB: DataB = { key: "b" };
const dAorB = Math.random() < 0.5 ? dA : dB;
funcAorB(dAorB, data => { data.key /* "a" | "b" */ });
// function funcAorB<DataA | DataB>(
//   data: DataA | DataB, callback: (data: DataA | DataB) => void
// ): void

这里,T是使用完全联合类型Data | DataB指定的。因此,将T限制为仅DataADataB是不正确的,并且将T重新约束为T extends DataAT extends DataB也是不正确的。通过选中data.key,你只能添加一个 lower-boundT,就像特性请求microsoft/TypeScript#14520中描述的那样,格式可能是T super DataA extends DataA | DataBT super DataB extends DataA | DataB,但是目前还没有办法在TypeScript中编写它。
如果TypeScript * 可以 * 做到这一点,那么它可能会注意到传递给funcAcallback必须是(data: DataA | DataB) => void(data: DataA) => void之间的类型,这是安全的,并且您的函数将编译无误。
有几个开放的特性请求要求支持基于控制流的泛型重新约束,其中一个是microsoft/TypeScript#33014,它允许使用泛型索引进行索引,就好像类型参数是下界一样。
另一个例子是microsoft/TypeScript#27808,它会显式禁止指定类型参数中的联合,毕竟,我想你实际上并不关心上面提到的dAorB的支持,而是想告诉编译器:“看,T要么是"a",要么是"b",但绝对不会是并集"a" | "b"“.如果我们 * 可以 * 使用类似T extends_oneof "a" | "b"T extends_oneof("a", "b")的东西,那么大概编译器可以像对待值收缩一样对待类型参数收缩,事情会神奇地工作。
无论如何,如果你关心这个被支持,你可能想去这些问题,给予他们一个👍,但务实地说,最好不要假设这将做任何事情,只是围绕它工作。
最简单的解决方法是像您一直在做的那样只使用类型Assert,更困难的是重构为使用泛型(如microsoft/TypeScript#47109中所述),以便您的funcAorB主体将执行编译器认为正确的单个操作(例如,funcs[data.key](data, callback)对应于一个适当的通用funcs)。既然您提到了这对您的用例不起作用,我就不在这里进一步讨论了。
Playground代码链接

相关问题