typescript 关于打印脚本联合类型的几个问题

lsmd5eda  于 2023-01-14  发布在  TypeScript
关注(0)|答案(2)|浏览(152)

我理解ts中的联合类型可以允许变量有多种类型。

type Test1 = never | any; 
// Any is the top-level type  
// Test1 = any
type Test2 = "123" | string;
// String is the collection of all strings
// Test2 = string

类型T=A | B,当A和B结合时当A(“广义类型”)包含B(“具体类型”)时,则得出A(“广义类型”)。
然后,当我应用“对象联合”的结论时,我对父子类型有疑问。

interface A1 {
    name: string
}
interface A2 extends A1 {
    age: number
}

type Test3 = A2 | A1;
 
// According to the conclusion, A2 is broader than A1, Test3 = A2
// Not equal to A2
/* Here I explain that when the value corresponding to Test3 is {name: "??"}, 
the mandatory item age attribute type in A2 type is unsafe
/

// According to the above explanation, I try to add options to A2. Will the result be different?
type Test4 = A1 | Partial<A2>;
// but, Not equal to A2 ?

type TypeKeys = keyof Test3;
// Why do I get such a result when I try to get the key
// TypeKeys = "name"

应用程序函数返回时也有问题

const record: Test3 = {
    name: 'name',
    age: 20
} 

const record2: Test3 = {
    name: 'name'
}

// Finally, I use Test3 type for function return
const fn = (): Test3 => record;

const da = fn();
da.name
da.age // The type shown here is unsafe
// Property 'age' does not exist on type 'Test3'.
// Property 'age' does not exist on type 'A1'
ecfdbz9o

ecfdbz9o1#

Test2Test3之间存在概念上的差异:

  • "123"已经存在于string集合中,即"123"**是string集合的子集。因此,该并集可以有效地折叠到string集合中
  • A1不是A2的子集,反之亦然,尽管乍一看这似乎违反直觉:
  • A1是具有单一属性name: string的对象
  • A2是具有两个属性name: stringage: number的对象
  • 没有对象可以定义为同时满足这两种定义,因此当您编写A1 | A2时,编译器最多只能解析为A1A2,但肯定不能同时满足这两种定义。
  • 注意:这个属性实际上非常强大,允许我们利用discriminated unions之类的东西

定义recordrecord2时,将执行以下操作:

  • recordrecord2注解为Test3,其等效于A1 | A2
  • 您将A2形式的对象传递给record,编译器对此非常满意,因为这是一个有效的A1 | A2。重要的是,并不是record在幕后变成了A2,它仍然是A1 | A2
  • 您将A1形状的对象传递给record2,编译器对此非常满意,因为这是一个有效的A1 | A2
  • 我发现如果你想象变量被定义为let而不是const会更容易形象化;只要变量在其生命周期内被赋值为A1A2形式的内容,编译器就会保持满意(即使它开始时是A2,将来也可能是A1等)

当所有的都说了和做了之后,尽管recordrecord2中的对象的内容对我们来说显然分别是A2A1,由于注解Test3,编译器不可能推断出底层对象是A1还是A2。关于Test3,它所能推断的是,不管当前值是多少,它都将具有name: string属性,它不知道age: number属性是存在还是缺失,因为这将取决于关于对象是A1还是A2的知识。
此问题的常见解决方案是使用type guard "解包"类型,例如:

function isA2(record: Test3): record is A2 {
  return (record as A2).age !== undefined;
}

function someFn() {
  const someVariable: Test3 = { name: 'someName' };

  if (isA2(someVariable)) {
    someVariable // A2
    someVariable.name // valid
    someVariable.age // valid
  }
  else {
    someVariable // A1
    someVariable.name // valid
    someVariable.age // invalid
  }
}

这显式地通知编译器正在使用运行时构造的基础类型的形状,因此即使变量的值要更改,它仍然能够保证类型安全。
现在应该可以理解为什么编译器不接受从fn定义中Test3类型的变量访问名为age的属性了。

const record: Test3 = {
    name: 'name',
    age: 20
}
const fn = (): Test3 => record;
const da = fn();

da.name // valid, this property definitely exists
da.age // invalid, this property may or may not exist

以下备选方案均有效
一个二个一个一个
https://tsplay.dev/w1AeGw

zlhcx6iw

zlhcx6iw2#

当您使用Test3的keyof时,TypeScript尝试获取Test3类型的键,即A2,因此它返回A2接口中的键,即仅“name”。

你可以通过使用类型保护来解决这个问题,检查返回的对象是否有age属性,

const fn = (): Test3 => {
    if(record.hasOwnProperty('age')){
        return record;
    }
    return record2;
}

相关问题