【问题标题】:Infer type for multiple properties to be the same推断多个属性的类型相同
【发布时间】:2021-06-06 21:29:51
【问题描述】:

是否可以在 Typescript 中将多个属性推断为同一类型,或者可能根据特定属性推断类型..

假设我们有 ->

interface Test<T> {
  value1: T;
  value2: T;
}

const test1:Test<string> = {
    value1: 'string1',
    value2: 'string1'
}

在上面,如果我将 value1 或 value2 变成一个数字,它会出错,太好了。但最好删除通用的&lt;string&gt;,让它基于可能的第一个属性类型value1

起初我以为我可以将 T 的默认值用作 any,然后它会推断类型,但它只是保持为 any,如下所示,我们有一个字符串和一个数字。

interface Test<T = any> {
  value1: T;
  value2: T;
}

const test1:Test = {
    value1: 'string1',
    value2: 2
}

【问题讨论】:

  • 不幸的是,由于 typescript 推断联合类型,这非常困难,所以它会推断 string | number 来解决问题。

标签: typescript


【解决方案1】:

如您所见,generic type parameter default 没有您想要的效果。如果您将类型称为Test 而不指定Test&lt;string&gt; 中的类型参数,编译器将使用默认值并将其解释为Test&lt;any&gt;。它不会为您推断string。有一个功能请求,microsoft/TypeScript#10671,要求提供一种方法让编译器推断未指定的类型参数......类似于Test&lt;infer&gt;Test&lt;*&gt;。但是目前 TypeScript 还没有这个功能。

您可以做的是创建一个通用实用程序函数来返回其输入:

const asTest = <T,>(test: Test<T>) => test;

当您调用泛型函数时,TypeScript 将尝试为测试参数推断出适当的类型。您可以调用此函数而不是注释变量:

const testString = asTest({
  value1: "string1",
  value2: "string2"
})
// const testString: Test<string>

const testStringNumber = asTest({
  value1: "string1",
  value2: 2 // error! 'number' is not assignable to 'string'
})
// const testString: Test<string>

这符合预期。


请注意,编译器是否会拒绝一个值或为它的类型参数合成一个union type 并不总是很明显。在testStringNumber 中,将T 推断为string | number 是合理的,但编译器使用的启发式方法是人们通常不想推断原始类型的联合。

另一方面,以下代码会导致推断出一个联合:

const testUnion = asTest({
  value1: { a: "" },
  value2: { b: "" }
})
/* Test<{
    a: string;
    b?: undefined;
} | {
    b: string;
    a?: undefined;
}> */

这种类型,Test&lt;{a: string; b?: undefined} | {b: string; a?: undefined}&gt; 确实准确地描述了传递给asTest() 的值的类型。编译器的启发式假设对象类型的联合更容易接受和预期。


但也许你更喜欢抱怨这种联合的东西,并且只使用(比如)value1 来推断T,然后只检查value2 反对它。也就是说,您希望value2 中的T 成为microsoft/TypeScript#14829 中要求的非推理类型参数用法。对此没有官方的直接支持,但有一些合理的方法可以模拟它。例如:

const asTest = <T, U extends T>(test: { value1: T, value2: U }): Test<T> => test;

这个版本的asTest 使用了两个泛型类型参数; T 对应于value1U 对应于value2Uconstrained 可分配给T,因此编译器将只接受value2 具有可分配给value1 的类型的输入。并且编译器只会通过查看value1 而不是value2 来推断TtestStringtestStringNumber 的效果相同:

const testString = asTest({ value1: "string1", value2: "string2" });
// const testString: Test<string>

const testStringNumber = asTest({
  value1: "string1",
  value2: 2 // error! 'number' is not assignable to 'string'
})
// const testString: Test<string>

但现在testUnion 产生错误:

const testUnion = asTest({
  value1: { a: "" },
  value2: { b: "" } // error! '{ b: string; }' is not assignable to '{ a: string; }'
})

因为{b: ""} 不能分配给从value1 推断的{a: string} 类型。


Playground link to code

【讨论】:

  • 谢谢,我还以为是我发疯了,每次尝试都感觉像是掉进了兔子洞。我确实在类型上直接尝试了U extends T,但这有鸡和蛋的问题:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-08-31
  • 2016-09-03
  • 2020-04-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多