函数如何根据参数是否未定义来知道确切的返回值?

时间:2018-11-09 08:06:09

标签: typescript

interface Activity {
    eat: () => void
}

interface Person {
    activity?: Activity
}

const activity = <T extends Person>(person: T) => ({
    eat: person.activity && person.activity.eat
})

const tom = {
    activity: {
        eat: () => {}
    }
}

const tomAct = activity(tom)
tomAct.eat() // should know `eat` does exist

const bobAct = activity({})
bobAct.eat // should know `eat` is undefined

您可以看到tomAct.eat将返回eat: (() => void) | undefined,但是在这种情况下tomAct知道eat: (() => void和bobAct是undefined

Typescript是否支持这种情况?我该怎么解决?

===

“打字稿”:“ ^ 3.1.2”,

2 个答案:

答案 0 :(得分:1)

Typescript是在编译时上运行的编译器,因此它只能知道当时已知的内容。

您的要求是运行时要求,某些属性的值仅在运行时才知道,因此无法使用TS。

答案 1 :(得分:1)

您的问题是控制流分析在泛型上并不能很好地工作。出于控制流分析的目的,编译器实质上是将T扩展为Person(找出person.activity && person.activity.eat的类型),因此推断出的activity()返回类型是与该函数的具体(非泛型)版本相同:

const activityConcrete = (person: Person) => ({
  eat: person.activity && person.activity.eat
}); // {eat: ()=>void | undefined}

为了获得所需的行为,您要么需要使编译器进行分析(有时是不可能的),要么只需assert即可得到期望的返回类型。传统上,您在这里要做的是使用overloads来表示输入和输出类型之间的关系:

function activity(person: { activity: Activity }): Activity;
function activity(person: { activity?: undefined }): { eat: undefined };
function activity(person: Person): { eat: Activity['eat'] | undefined };
function activity(person: Person): { eat: Activity['eat'] | undefined } {
  return {
    eat: person.activity && person.activity.eat
  }
}

从TypeScript 2.8开始,您可以使用conditional types来表示同一件事:

type PersonEat<T extends Person> = T['activity'] extends infer A ? 
  A extends Activity ? A['eat'] : undefined : never;

const activity = <T extends Person>(person: T) => ({
  eat: person.activity && person.activity.eat
} as { eat: PersonEat<T> })

这两种方法都会导致类似的行为:

const tom = {
  activity: {
    eat: () => {}
  }
}
const bob = {};

const tomAct = activity(tom)
tomAct.eat() // okay 

const bobAct = activity(bob)
bobAct.eat // undefined 

那行得通。


请注意,在没有Person的情况下,如何处理activity会有一点皱纹。上方bob的类型为{},对于对象来说,其类型为top type,这意味着它absorbs与您联合的任何其他对象类型。也就是说,在:

const tomOrBob = Math.random() < 0.5 ? tom : bob; // type is {}

可以推断出tomOrBob的类型为{} | {activity: Activity},它被折叠为{}。因此,编译器忘记了tomOrBob可能有一个activity。并导致以下错误行为:

const tomOrBobActivity = activity(tomOrBob);
tomOrBobActivity.eat; // undefined  but it should be (()=>void) | undefined

如果您对那种过度的不确定性还可以的话,那很好。否则,您需要明确告诉编译器记住activity中缺少bob

const bob: { activity?: undefined } = {}; // bob definitely is missing activity

const bobAct = activity(bob);
bobAct.eat // still undefined as desired 

const tomOrBob = Math.random() < 0.5 ? tom : bob;

const tomOrBobAct = activity(tomOrBob);
tomOrBobAct.eat; // (() => void) | undefined 

而且表现如预期。


好的,希望能有所帮助。祝你好运!