返回介绍

十五、高级类型

发布于 2024-09-07 18:09:17 字数 5267 浏览 0 评论 0 收藏 0

15.1 交叉类型(取并集)

交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。 例如, Person & Loggable 同时是 PersonLoggable 。就是说这个类型的对象同时拥有了这两种类型的成员。

我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用。 (在 JavaScript 里发生这种情况的场合很多!)下面是如何创建混入的一个简单例子

function extend<T, U> (first: T, second: U): T & U {
  let result = {} as T & U
  for (let id in first) {
  result[id] = first[id] as any
  }
  for (let id in second) {
  if (!result.hasOwnProperty(id)) {
    result[id] = second[id] as any
  }
  }
  return result
}

class Person {
  constructor (public name: string) {
  }
}

interface Loggable {
  log (): void
}

class ConsoleLogger implements Loggable {
  log () {
  // ...
  }
}

var jim = extend(new Person('Jim'), new ConsoleLogger())
var n = jim.name
jim.log()
interface DogInterface {
  run(): void
}
interface CatInterface {
  jump(): void
}

// pet 具备两个接口的所有方法
let pet: DogInterface & CatInterface = {
  run() {},
  jump() {}
}

// 联合类型
let a: number | string = 1
let b: 'a' | 'b' | 'c' // 字面量联合类型
let c: 1 | 2 | 3 // 数字联合类型


class Dog implements DogInterface {
  run() {}
  eat() {}
}
class Cat  implements CatInterface {
  jump() {}
  eat() {}
}
enum Master { Boy, Girl }
function getPet(master: Master) {
  let pet = master === Master.Boy ? new Dog() : new Cat();
  // pet.run()
  // pet.jump()
  pet.eat()
  return pet
}

interface Square {
  kind: "square";
  size: number;
}
interface Rectangle {
  kind: "rectangle";
  width: number;
  height: number;
}
interface Circle {
  kind: "circle";
  radius: number;
}

type Shape = Square | Rectangle | Circle

function area(s: Shape) {
  switch (s.kind) {
    case "square":
      return s.size * s.size;
    case "rectangle":
      return s.height * s.width;
    case 'circle':
      return Math.PI * s.radius ** 2
    default:
      return ((e: never) => {throw new Error(e)})(s)
  }
}
console.log(area({kind: 'circle', radius: 1}))

15.2 索引类型

let obj = {
  a: 1,
  b: 2,
  c: 3
}

// function getValues(obj: any, keys: string[]) {
//   return keys.map(key => obj[key])
// }
function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
  return keys.map(key => obj[key])
}
console.log(getValues(obj, ['a', 'b']))
// console.log(getValues(obj, ['d', 'e']))

// keyof T
interface Obj {
  a: number;
  b: string;
}
let key: keyof Obj

// T[K]
let value: Obj['a']

// T extends U

15.3 映射类型

interface Obj {
  a: string;
  b: number;
}

// 使得每个成员属性变为只读
type ReadonlyObj = Readonly<Obj>

// 把一个接口属性变为可选
type PartialObj = Partial<Obj>

// 抽取 obj 的子集
type PickObj = Pick<Obj, 'a' | 'b'>

type RecordObj = Record<'x' | 'y', Obj>

15.4 条件类型

// T extends U ? X : Y

type TypeName<T> =
  T extends string ? "string" :
  T extends number ? "number" :
  T extends boolean ? "boolean" :
  T extends undefined ? "undefined" :
  T extends Function ? "function" :
  "object";
type T1 = TypeName<string>
type T2 = TypeName<string[]>

// (A | B) extends U ? X : Y
// (A extends U ? X : Y) | (B extends U ? X : Y)
type T3 = TypeName<string | string[]>

type Diff<T, U> = T extends U ? never : T
type T4 = Diff<"a" | "b" | "c", "a" | "e">
// Diff<"a", "a" | "e"> | Diff<"b", "a" | "e"> | Diff<"c", "a" | "e">
// never | "b" | "c"
// "b" | "c"

type NotNull<T> = Diff<T, null | undefined>
type T5 = NotNull<string | number | undefined | null>

// Exclude<T, U>
// NonNullable<T>

// Extract<T, U>
type T6 = Extract<"a" | "b" | "c", "a" | "e">

// ReturnType<T>
type T8 = ReturnType<() => string>

15.5 联合类型

联合类型与交叉类型很有关联,但是使用上却完全不同。 偶尔你会遇到这种情况,一个代码库希望传入 numberstring 类型的参数。 例如下面的函数

function padLeft(value: string, padding: any) {
  if (typeof padding === 'number') {
  return Array(padding + 1).join(' ') + value
  }
  if (typeof padding === 'string') {
  return padding + value
  }
  throw new Error(`Expected string or number, got '${padding}'.`)
}

padLeft('Hello world', 4) // returns "  Hello world"

padLeft 存在一个问题,padding 参数的类型指定成了 any。 这就是说我们可以传入一个既不是 number 也不是 string 类型的参数,但是 TypeScript 却不报错

let indentedString = padLeft('Hello world', true) // 编译阶段通过,运行时报错

为了解决这个问题,我们可以使用 联合类型做为 padding 的参数

function padLeft(value: string, padding: string | number) {
  // ...
}

let indentedString = padLeft('Hello world', true) // 编译阶段报错
  • 联合类型表示一个值可以是几种类型之一。我们用竖线( | )分隔每个类型,所以 number | string 表示一个值可以是 numberstring

如果一个值是联合类型, 我们只能访问此联合类型的所有类型里共有的成员

interface Bird {
  fly()
  layEggs()
}

interface Fish {
  swim()
  layEggs()
}

function getSmallPet(): Fish | Bird {
  // ...
}

let pet = getSmallPet()
pet.layEggs() // okay
pet.swim()  // error

这里的联合类型可能有点复杂:如果一个值的类型是 A | B ,我们能够确定的是它包含了 AB 中共有的成员。这个例子里,Fish 具有一个 swim 方法,我们不能确定一个 Bird | Fish 类型的变量是否有 swim 方法。 如果变量在运行时是 Bird 类型,那么调用 pet.swim() 就出错了

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文