TypeScript 交叉类型

321 阅读4分钟

我正在参加「掘金·启航计划」

前言

除了上文说到的 联合类型

TS 类型系统还有哪些运算? 交叉类型 itersection types (交集)

交叉类型

type A = string & number
// A 的结果是 never

一般来说在做交叉运算的时候,不会用到简单类型上,只会用到对象上面。

type 有左手的人 = {
  left: string
}
type 有右手的人 = {
  right: string 
}

// 有左手的人 并 有右手的人
type C = 有左手的人 | 有右手的人   
// 有左手的人 交 有右手的人 
type D = 有左手的人 & 有右手的人

const d: D = {
  // 只给 left 不给 right 它就会报错 right is missing
  left: 'yes'
}
type 有左手的人 = {
  left: string
}

const a: 有左手的人 = {
  left: '一米八',
  right: '一米五' // 报错
}

Screen Shot 2022-10-06 at 11.32.59 AM.png

根据交集图所示,有左手的人确实有可能有右手的。那么以上问题在哪?

TS 有时候松有时候紧


type 有左手的人 = {
  left: string
}

const b = {
  left: '一米八',
  right: '一米五'
}

// 没有报错
const a: 有左手的人 = b

// 报错
const a2: 有左手的人 = {
  left: '一米八',
  right: '一米五'
}

当你在声明的时候,初始化的那一刻,你是不能有杂念的,也就是说 初始化的时候不能有任何东西

接口也能求交集

交集不一定只用在 type 上面

interfaceof Colorful {
  color: string
}

interface Circle {
  radius: number
}

type ColorfulCircle = Colorful & Circle

只要描述的是对象,不管是 type 描述,还是 interfaceof 描述,都可以。

模拟 User 继承 Person

type Person = {
  name: string
  age: number
}

type User = Person & {
  id: number
  email: string
}

const u: User = {
  name: 'hone', age: 18, id: 1,
  email: 'xxxx@gmail.com'
}

交叉类型的特殊情况

type Person = {
  name: string
  age: number
  // 属性冲突,未报错
  id: string
}

type User = Person & {
  id: number
  email: string
}

// 合并之后,这个 id 是 number 还是 string ?
// id 和 id 要求交集,最后 id 是 never
const u: User = {  // 这个 User 类型不能用,但是并不报错
//  id: 1, // never  
  name: 'hone', age: 18, 
  email: 'xxxx@gmail.com',
  id: (1) as never  // 可以自己骗自己写一个
}

当你在使用 type 做交集的时候,当 id 冲突了(只要有一个字段冲突了),整个类型虽然不报错,但并不能用,就好像 这个 User 类型实际上是 never。

type Person = {
  name: string
  id: "A"
}

type User = Person & {
  id: "B"
  email: string
}

const u: User = {  // 这个 User 类型不能用,但是并不报错
//  id: 1, // never  
  name: 'hone',
  email: 'xxxx@gmail.com',
  id: (1) as never  // 可以自己骗自己写一个
}

Screen Shot 2022-10-06 at 4.51.03 PM.png

// 如果把 id 改一哈
// 写一种骗自己的写法
type Person = {
  name: string
  id: "A"
}

type User = Person & {
  id: "B"
  email: string
}

const u: User = {  // User 是 never
 // 这个就不可以用了
 // 因为 u 这个值是含有 never 的一个对象,而这个对象是不能赋值给 never 的 
  email: 'x' as never,
  name: 'hone' as never
}


const a: User = {  // User 是 never
  email: 'x',
  name: 'hone'
} as never  // 除非在外面写成这样

Screen Shot 2022-10-06 at 5.27.51 PM.png

type 和 interface 的第四个区别

interface Person  {
  id: string
  name: string
}

interface User extends Person {
  id: number
  email: string
}

const a: User = {
  email: 'x',
  name: 'hone',
  id: 1
}

Screen Shot 2022-10-06 at 6.50.02 PM.png

用 type 不会报错,type 只会给你弄一个 never 出来,那个时候可能还没发现自己写错了,只有在用到 never值的时候才会发现,没有值可以用。

如果你希望你的东西,可扩展性强一点,尽量使用 interface, 那什么东西可拓展性强,就是对外提供的接口,容易被用户去扩展的,对内就没有必要去用 interface, 很少会自己扩展自己,因为可以自己改(内部可以随便改),扩展的多了就会导致层级过深很难用这个现象。

第四个区别:遇到属性冲突的时候,interface 会直接报错,而 type 只会弄出一个 never 。

其实 never 也可以看做一个 报错

type A = {
  method: (n: number) => void
}

type B = {
  method: (n: string) => void
} & A

// 请问 B 它的 method 还存不存在 ?
const b: B = {
  method: (n) => {
    n  // number | string
  }
}

console.log(b)
// 可以写 method ,那就是说 这两个 method 没有冲突 ?

以上写出来没有报错,那就说明有

type F1 = (n: number) => void
type F2 = (n: string) => void
type X = F1 & F2

// 函数的交集会得到一个参数并集
const x: X = (n) => { // number | string
  console.log(n)
}

两个函数求交集会得到一个参数的并集

结论

交叉类型常用于有交集的类型 A、B

如果 A、B 无交集,可能得到 never,也可能只是属性是 never。

只要 never 出现了,这个类型它就不能用

理解 type A = {name: 'string'}

type A = {
  name: string
}

type B = {
  age: number
}

type C = A | B

const d = {
  name: 'hone', gender: '男'
}

const c:C = d  // 请问, d 赋值给 c 后 TS 会报错吗?

答案:不报错,d 可以赋值为 c, 因为这个 d 是 A 类型

type A = {name: string} 它并不单单指 name 为一个 key 的对象,它只是说 name 为 string,并没有做其他的限制。

type A = {
  name: string
}

// 1. TS 在第一次声明的时候,它做的是严格检查
// 4. 第一次声明的时候这里别多加东西
const a: A = {
  name: 'hone' , gender: '男'
}
// 2. 如果你这个对象是之前就声明好的,它就不做严格检查
type A = {
  name: string
}

const a1 = {
  name: 'hone' , gender: '男'
}

// 3. 因为你多一个属性对类型 A 来说无所谓
const a: A = a1