Typescript查找类型 - 正确缩小与Partial合并时设置的属性

时间:2017-01-11 19:49:24

标签: javascript typescript typescript2.1

我正在使用查找类型,并希望构建一种安全合并的util函数(一个采用类型T的实体和一个包含T的键子集的对象更新)。我的目标是让编译器告诉我何时拼错一个属性或尝试为T添加不存在的属性。

所以我有Person并在(v2.1)中使用内置Partial,如下所示:

interface Person {
  name: string
  age: number
  active: boolean
}

function mergeAsNew<T>(a: T, b: Partial<T>): T {
  return Object.assign({}, a, b);
}

现在我将其应用于以下数据:

let p: Person = {
  name: 'john',
  age: 33,
  active: false
};

let newPropsOk = {
  name: 'john doe',
  active: true
};

let newPropsErr = {
  fullname: 'john doe',
  enabled: true
};

mergeAsNew(p, newPropsOk);
mergeAsNew(p, newPropsErr); // <----- I want tsc to yell at me here because of trying to assign non-existing props

我希望TS编译器在第二次调用时对我大喊大叫fullnameenabled不是Person的道具。不幸的是,这在当地编译得很好,但是......当我在online TS Playground中做同样的事情时,我会得到或多或少的期望:

The type argument for type parameter 'T' cannot be inferred from the usage. Consider specifying the type arguments explicitly.
  Type argument candidate 'Person' is not a valid type argument because it is not a supertype of candidate '{ fullname: string; enabled: boolean; }'.
    Property 'name' is missing in type '{ fullname: string; enabled: boolean; }'.

看起来这个游乐场使用与我在本地做同样的版本(2.1.4)。有没有人知道为什么这两者可能有所不同?

加分问题:

当我尝试以下任务时:

let x: Person = mergeAsNew(p, newPropsOk);

我在x上收到以下错误,但仅限于操场上(本地一切都很好):

Type '{ name: string; active: boolean; }' is not assignable to type 'Person'.
  Property 'age' is missing in type '{ name: string; active: boolean; }'.

为什么?不应该 属于Person类型,因为第一个mergeAsNew参数 Person而其他所有内容都是{{ 1}} - 道具子集(所以它最多Person)?

修改 这是我的Person

tsconfig.json

2 个答案:

答案 0 :(得分:1)

当你想表达一种类型只有另一种类型的属性子集时,普通的extends也可以提供帮助

interface Person {
    name: string
    age: number
    active: boolean
}


let p: Person = {
    name: 'john',
    age: 33,
    active: false
};

let newPropsOk = {
    name: 'john doe',
    active: true
};

let newPropsErr = {
  fullname: 'john doe',
  enabled: true
};

// a extends b and not the other way round
//because we don't want to allow b to have properties not in a
function mergeAsNew<T2, T1 extends T2>(a: T1, b: T2): T1 {
    return Object.assign({}, a, b);
}

mergeAsNew(p, newPropsOk); // ok
mergeAsNew(p, newPropsErr); 
// Argument of type 'Person' is not assignable 
// to parameter of type '{ fullname: string; enabled: boolean; }'.
//   Property 'fullname' is missing in type 'Person'.

PS不知道在游乐场和地图类型上发生了什么

答案 1 :(得分:0)

好吧,看起来我昨天太固定了。我有一些睡眠,看起来(可能)不可能做我想要的那样(使用Partial)。抛开这种奇怪的游乐场行为一段时间,这就是为什么(至少我是这么认为):

回顾一下:

interface Person {
  name: string
  age: number
  active: boolean
}

function mergeAsNew<T>(a: T, b: Partial<T>): T {
  return Object.assign({}, a, b);
}

在此代码中,Partial<Person>可能(但不必)包含任何Person道具,因此以下内容完全有效Partial<Person>类型:

let newPropsErr = {
  fullname: 'john doe',
  enabled: true
};

由于Typescript具有结构类型,它不关心额外的道具,它只检查对象的形状,这里形状适合Partial<Person>就好了。

以某种方式再次检查参数形状Partial<Person>以获取额外道具的唯一方法是将其作为对象文字传递(严格检查文字以匹配类型)

现在mergeAsNew功能本身。我在这里Object.assign完全按照它的设计 - 将两个args的道具合并到新对象(所有这些)中。因为类型仅用于TS编译,所以在运行时无法限制并有选择地从b中选择道具并将其应用于覆盖a道具。

看起来@Artem's answer使用extend执行我需要的操作而没有映射/查找类型。

无论如何,我仍然不明白为什么TS Playground以它的工作方式工作(最初给我的印象是所有这些东西都非常可行)。