简体   繁体   English

基于种类的类型推断的打字稿语法

[英]Typescript Syntax for Type Inference based on kind

I have an issue writing proper typescript syntax for strict infering where:我在编写正确的打字稿语法以进行严格推断时遇到问题:

  1. compiler properly reports on missing switch/case option编译器正确报告缺少的 switch/case 选项
  2. returned value matches input kind by type返回值按类型匹配输入种类
type KindA = {kind:'a'};
type KindB = {kind:'b'};
type KindC = {kind:'c'};
type AllKinds = KindA | KindB | KindC;

function create<T extends AllKinds>(kind:AllKinds['kind']):T {
  switch(kind) {
    case "a": return {kind:'a'};
    case "b": return {kind:'b'};
    case "c": return {kind:'c'};
  }
}

create("a");

Playground 操场

I wonder if this is possible with the latest Typescript.我想知道最新的 Typescript 是否可以实现。

With my other approach (ie case "a": return {kind:'b'} as T; ) the returned value is not type checked to what I need.使用我的另一种方法(即case "a": return {kind:'b'} as T; ),返回的值不会根据我的需要进行类型检查。

It is unsafe to return T in your case.在您的情况下返回T是不安全的。

See why:看看为什么:

type KindA = { kind: 'a' };
type KindB = { kind: 'b' };
type KindC = { kind: 'c' };
type AllKinds = KindA | KindB | KindC;

function create<T extends AllKinds>(kind: AllKinds['kind']): T {
  switch (kind) {
    case "a": return { kind: 'a' };
    case "b": return { kind: 'b' };
    case "c": return { kind: 'c' };
  }
}

const result = create<{ kind: 'a', WAAAT: () => {} }>("a")
result.WAAAT() // compiler but causes an error in runtime

Generic argument should in 90% of cases depend on input value.通用参数应该在 90% 的情况下取决于输入值。

See this example:看这个例子:

type KindA = { kind: 'a' };
type KindB = { kind: 'b' };
type KindC = { kind: 'c' };
type AllKinds = KindA | KindB | KindC;


const builder = <Kind extends AllKinds['kind']>(kind: Kind) => ({ kind })

const result = builder("a"); // {kind: 'a' }

Playground 操场

See this answer, this answer and my article for more context有关更多上下文,请参阅答案、 答案和我的文章

Do I get it right that witch current Typescript, one can not have both ?我是否正确地认为女巫当前的 Typescript,一个不能同时拥有?

The problem is not in switch statement but rather in explicit return type.问题不在于switch语句,而在于显式返回类型。 Return type can't depend on a generic value which is not binded with function arguments.返回类型不能依赖于未与函数参数绑定的泛型值。

In fact, it is possible to achieve waht you want:事实上,有可能实现你想要的:

type KindA = { kind: 'a' };
type KindB = { kind: 'b' };
type KindC = { kind: 'c' };
type AllKinds = KindA | KindB | KindC;

function create<Kind extends AllKinds['kind']>(kind: Kind): Extract<AllKinds, { kind: Kind }>
function create(kind: AllKinds['kind']) {
    switch (kind) {
        case "a": return { kind: 'a' };
        case "b": return { kind: 'b' };
        case "c": return { kind: 'c' };
    }
}

const result1 = create("a") // KindA
const result2 = create("b") // KindB
const result3 = create("c") // KindC

Playground 操场

As you might have noticed, I have used function overloading .您可能已经注意到,我使用了函数重载 It makes TS compiler less strict.它使 TS 编译器不那么严格。 In other words, provides a bit of unsafety but in the same time makes it more readable and infers return type.换句话说,提供了一些不安全性,但同时使其更具可读性并推断返回类型。

AFAIK, function overloads behaves bivariantly . AFAIK,函数重载的行为是双变的 Hence, it is up to you which option is better因此,由您决定哪个选项更好

The error is quite descriptive.该错误是非常具有描述性的。

Type '{ kind: "a"; }' is not assignable to type 'T'.
  '{ kind: "a"; }' is assignable to the constraint of type 'T',
  but 'T' could be instantiated with a different subtype of constraint 'AllKinds'.

This problem occurs even when you use a simpler type constraint.即使您使用更简单的类型约束,也会出现此问题。

function foo<T extends string>(): T {
  return 'foo';
}

Here we'd get the following error.在这里我们会得到以下错误。

Type 'string' is not assignable to type 'T'.
  'string' is assignable to the constraint of type 'T',
  but 'T' could be instantiated with a different subtype of constraint 'string'.

The problem is that we said that we'd return something of type T , but T is not the same type as string .问题是我们说过我们会返回T类型的东西,但Tstring类型不同。 Yes, the type T extends string but all that means is that T is a subtype of string .是的,类型T扩展了string但这意味着Tstring的子类型。 For example, the type 'bar' is a subtype of string .例如,类型'bar'string的子类型。 Hence, we can instantiate T with 'bar' .因此,我们可以用'bar'实例化T Hence, we'd expect the return value to be 'bar' but the return value is 'foo' .因此,我们希望返回值是'bar'但返回值是'foo'

The solution is to simply not use generics.解决方案是简单地不使用泛型。 If you want to return a string then just say that you're returning a string .如果你想返回一个string那么只需说你正在返回一个string Don't say that you're returning a value of some subtype T of string .不要说您要返回string的某个子类型T的值。

function foo(): string {
  return 'foo';
}

Similarly, if you want to return a value of type AllKinds then just say that you're returning a value of type AllKinds .同样,如果您想返回AllKinds类型的值,那么只需说您正在返回AllKinds类型的值。 Don't say that you're returning a value of some subtype T of AllKinds .不要说您要返回AllKinds的某个子类型TAllKinds

type KindA = {kind:'a'};
type KindB = {kind:'b'};
type KindC = {kind:'c'};
type AllKinds = KindA | KindB | KindC;

function create(kind:AllKinds['kind']): AllKinds {
  switch(kind) {
    case "a": return {kind:'a'};
    case "b": return {kind:'b'};
    case "c": return {kind:'c'};
  }
}

create("a");

Edit: You need dependent types to do what you want.编辑:你需要依赖类型来做你想做的事。 TypeScript doesn't have dependent types. TypeScript 没有依赖类型。 However, you can create a custom fold function that provides additional type safety.但是,您可以创建提供额外类型安全性的自定义折叠函数。

type Kind = 'a' | 'b' | 'c';

type KindA = { kind: 'a' };
type KindB = { kind: 'b' };
type KindC = { kind: 'c' };

type AllKinds = KindA | KindB | KindC;

function foldKind<A, B, C>(a: A, b: B, c: C): (kind: Kind) => A | B | C {
  return function (kind: Kind): A | B | C {
    switch (kind) {
      case 'a': return a;
      case 'b': return b;
      case 'c': return c;
    }
  }
}

const create: (kind: Kind) => AllKinds = foldKind<KindA, KindB, KindC>(
  { kind: 'a' },
  { kind: 'b' },
  { kind: 'c' }
);

Now, you can only provide a value of KindA for 'a' , a value of KindB for 'b' , and a value of KindC for 'c' .现在,你只能提供价值KindA'a' ,值KindB'b' ,和值KindC'c' See the demo for yourself.自己看演示

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM