Typescript 实战 --- (8)高级类型

1、交叉类型

将多个类型合并成一个类型,新的类型将具有所有类型的特性,适用于对象混用

语法:

类型1 & 类型2 & 类型3
interface CatInterface {
  run(): void
}

interface DogInterface {
  jump(): void
}

// 交叉类型具有所有类型的特性
let pet: CatInterface & DogInterface = {
  run() {},
  jump() {}
}

2、联合类型

声明的类型并不确定,可以为多个类型中的一个。用竖线(|)分隔每个类型,所以number | string | boolean表示一个值可以是number,string,或boolean

let a: number | string = 2;
a = ‘hello‘;
a = undefined; // 可以为其子类型

a = true;  // Error: 不能将类型“true”分配给类型“string | number”

(1)、字面量联合类型:不仅限制类型,还限制取值

// 字符串联合类型
let x: ‘typescript‘ | ‘webpack‘ | ‘nodejs‘;

x = ‘webpack‘;
x = ‘hello‘;   // Error: 不能将类型“"hello"”分配给类型“"typescript" | "webpack" | "nodejs"”

// 数字联合类型
let y: 1 | 2 | 3;

y = 3;
y = 33;       // Error: 不能将类型“33”分配给类型“1 | 2 | 3”

let z: ‘typescript‘ | 2;

z = ‘typescript‘;
z = 2;
z = 1;       // Error: 不能将类型“1”分配给类型“"typescript" | 2”

(2)、对象联合类型:在类型未确定的情况下,只能访问所有类型的公用成员

enum Pet { Dog, Cat };

interface DogInterface {
  run(): void;
  eat(): void;
}

interface CatInterface {
  jump(): void;
  eat(): void;
}

class Dog implements DogInterface {
  run() {};
  eat() {};
}

class Cat implements CatInterface {
  jump() {};
  eat() {};
}

function getPet(pet: Pet) {
  // let smallPet: Dog | Cat
  let smallPet = pet === Pet.Dog ? new Dog() : new Cat();

  // 类型不确定时,只能取公有成员
  smallPet.eat();

  smallPet.run();   // 类型“Dog | Cat”上不存在属性“run”
  smallPet.jump();  // 类型“Dog | Cat”上不存在属性“jump”

  return smallPet;
}

(3)、可区分的联合类型:这种模式从本质上来讲是结合了联合类型和字面量联合类型的一种类型保护方法

其核心思想是:如果一个类型是多个类型的联合类型,并且每个类型之间有一个公共的属性,那么就可以利用这个公共的属性创建不同的类型保护区块

// 例如:Shape是多个类型的联合类型,每个类型都具有一个公共属性kind,由此在 switch中建立了不同类型的保护区块

interface Rectangle {
  kind: ‘rectangle‘;
  width: number;
  height: number;
}

interface Square {
  kind: ‘square‘;
  size: number;
}

type Shape = Rectangle | Square;

function area(s: Shape) {
  switch(s.kind) {
    case ‘rectangle‘:
      return s.width * s.height;
    case ‘square‘:
      return s.size * s.size;
  }
}

如果又添加了一个联合类型,但是又没有在 area 函数中设定类型保护区块,会发生什么呢?

interface Rectangle {
  kind: ‘rectangle‘;
  width: number;
  height: number;
}

interface Square {
  kind: ‘square‘;
  size: number;
}

// 添加新的联合类型
interface Circle {
  kind: ‘circle‘;
  r: number;
}

type Shape = Rectangle | Square | Circle;

function area(s: Shape) {
  switch(s.kind) {
    case ‘rectangle‘:
      return s.width * s.height;
    case ‘square‘:
      return s.size * s.size;
  }
}

console.log(area({ kind: ‘circle‘, r: 1 }));   // undefined

执行程序打印出了一个结果 undefined,由于上例中并没有在 area 方法中为 Circle 指定计算面积的方法,理论上应该提示错误,而不是直接返回 undefined。

为了让编译器正确的提示错误,有两种可选方法:

(1)、为 area 方法指定返回值类型

function area(s: Shape): number {
  switch(s.kind) {
    case ‘rectangle‘:
      return s.width * s.height;
    case ‘square‘:
      return s.size * s.size;
  }
}

(2)、利用never类型

// 给定一个 default 分支,通过判断 s 是不是 never 类型来提示错误。
// 如果是 never 类型,则可以在前面的分支中找到对应的执行代码;
// 如果不是 never 类型,则说明前面的代码有遗漏,需要补全

function area(s: Shape): number {
  switch(s.kind) {
    case ‘rectangle‘:
      return s.width * s.height;
    case ‘square‘:
      return s.size * s.size;
    default:
      return ((e: never) => { throw new Error(e) })(s);
      // 类型“Circle”的参数不能赋给类型“never”的参数
  }
}

通过错误提示补全代码

function area(s: Shape): number {
  switch(s.kind) {
    case ‘rectangle‘:
      return s.width * s.height;
    case ‘square‘:
      return s.size * s.size;
    case ‘circle‘:
      return Math.PI * s.r ** 2
    default:
      return ((e: never) => { throw new Error(e) })(s);
  }
}

console.log(area({ kind: ‘circle‘, r: 1 }));   // 3.141592653589793

3、索引类型

使用索引类型,编译器就能够检查使用了动态属性名的代码。例如:从js对象中选取属性的子集,然后建立一个集合

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

function getValues(obj: any, keys: string[]) {
  return keys.map(key => obj[key])
}

// obj 中存在的属性
console.log(getValues(obj, [‘a‘, ‘b‘]));   // [ 1, 2 ]

// obj 中不存的属性,返回 undefined,而没有提示报错
console.log(getValues(obj, [‘e‘, ‘f‘]));   // [ undefined, undefined ]

索引类型可以用来解决上例中的问题,在认识索引类型之前需要先了解几个概念:

(1)、索引类型查询操作符   keyof T

对于任何类型T,keyof T 的结果是 类型T的所有公共属性的字面量的联合类型

interface Person {
  name: string;
  gender: string;
  age: number;
}

let personProps: keyof Person;  // ‘name‘ | ‘gender‘ | ‘age‘
console.log(personProps)

(2)、索引访问操作符     T[K]

类型T的属性K所代表的类型

interface Person {
  name: string;
  gender: string;
  age: number;
}

let n: Person[‘name‘];   // n 的类型是 string
let a: Person[‘age‘];    // a 的类型是 number

(3)、泛型约束      T extends U

表示泛型变量可以继承某个类型获得某些属性

结合以上三点来改造 getValues 函数

// 1、用T来约束obj
// 2、用K来约束keys数组
// 3、给K增加一个类型约束,让它继承obj的所有属性的联合类型
// 4、函数的返回值是一个数组,数组的元素的类型就是属性K对应的类型

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

function getValues<T, K extends keyof T>(obj: T, keys: K[]): T[K][] {
  return keys.map(key => obj[key])
}

// obj 中存在的属性
console.log(getValues(obj, [‘a‘, ‘b‘]));   // [ 1, 2 ]

console.log(getValues(obj, [‘e‘, ‘f‘]));
// Error:不能将类型“string”分配给类型“"a" | "b" | "c"”

4、映射类型

通过映射类型,可以从一个旧的类型生成一个新的类型,比如把一个类型中的所有属性变成只读

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

4-1、同态

同态的意思是:只会作用于旧类型的属性,而不会引入新的属性

(1)、Readonly<T>   将旧类型中的每一个成员都变成只读

type ReadonlyObj = Readonly<Obj>;

(2)、Partial<T>   把旧类型中的每一个成员都变成可选的

type PartialObj = Partial<Obj>;

(3)、Pick<T, key1 | key2 | keyn>   可以抽取旧类型中的一些子集

接受两个参数:第一个是要抽取的对象,第二个是要抽取的属性的key

type PickObj = Pick<Obj, ‘a‘ | ‘c‘>;

4-2、非同态,会创建一些新的属性

(1)、Record<key1 | key2 | keyn, T>

接受两个参数:第一个参数是一些预定义的新的属性,第二个参数是一个已知的对象

type RecordObj = Record<‘x‘ | ‘y‘, Obj>;

映射类型的本质是一些预定义的泛型接口,通常还会结合索引类型来获取对象的属性和属性值,从而将一个对象映射成想要的结构

原文地址:https://www.cnblogs.com/rogerwu/p/12214808.html

时间: 2024-11-06 03:55:45

Typescript 实战 --- (8)高级类型的相关文章

从C#到TypeScript - 高级类型

C# vs TypeScript - 高级类型 上一篇讲了基础类型,基本上用基础类型足够开发了,不过如果要更高效的开发,还是要看下高级类型,这篇和C#共同点并不多,只是延用这个主题. 联合类型 可以从字面上进行理解:其实就是多个类型联合在一起,用|符号隔开.如: string | number, 表示希望这个类型既可以是string,又可以是number.联合类型的字段只能调用这些类型共同拥有的方法,除非类型推论系统自动判断出真正的类型. //这里sn就是一个联合类型的字段,由于类型推论推断出s

013 --TypeScript之高级类型

交叉类型可以简单理解为将多个类型合并成一个类型 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

【Go入门教程3】基本类型 和 高级类型

基本类型 Go 有很多预定义类型,这里简单地把它们分为 基本类型 和 高级类型.Go 的基本类型并不多,而且大部分都与整数相关,如下表所示: 名 称 宽度(字节) 零 值 说 明 bool 1 false 布尔类型,其值不为真即为假.真用常量 true 表示,假由常量 false 表示 byte 1 0 字节类型,它也可以看作是一个由 8 位二进制数表示的无符号整数类型 rune 4 0 rune 类型,它是有 Go 语言定义的特有的数据类型,专用于存储 Unicode 字符.它也可以看作一个由

TS(6)-- 类型推论、类型兼容性、高级类型

2019-11-09: 学习内容:类型推论.类型兼容性.高级类型 一.类型推论:类型是在哪里如何被推断的 在有些没有明确指出类型的地方,类型推论会帮助提供类型.如:let x = 3;  变量x的类型被推断为数字. 这种推断发生在初始化变量和成员,设置默认参数值和决定函数返回值时.大多数情况下,类型推论是直截了当地. 最佳通用类型:如: let x = [0, 1, null];   两种选择:number 和 null , 计算通用类型算法会考虑所有的候选类型,并给出一个兼容所有候选类型的类型

MapReduce编程实战之“高级特性”

本篇介绍MapReduce的一些高级特性,如计数器.数据集的排序和连接.计数器是一种收集作业统计信息的有效手段,排序是MapReduce的核心技术,MapReduce也能够执行大型数据集间的""连接(join)操作. 计数器 计数器是一种收集作业统计信息的有效手段,用于质量控制或应用级统计.计数器还可用于辅助诊断系统故障.对于大型分布式系统来说,获取计数器比分析日志文件容易的多. 示例一:气温缺失及不规则数据计数器 import java.io.IOException; import

【TypeScript】TypeScript 学习 1——基本类型

TypeScript 是 JavaScript 的超集,TypeScript 经过编译之后都会生成 JavaScript 代码.TypeScript 最大的特点就是类型化,因此才叫做 TypeScript.比起弱类型的 JavaScript,类型化的 TypeScript 显得更加容易维护. 在 TypeScript 中一共有 7 种基本类型. 1.boolean var isDone: boolean = false; 2.number 代表 JavaScript 中的数字.在 JavaScr

scala一些高级类型

package com.ming.test import scala.collection.mutable.ArrayBuffer import scala.io.Source import java.awt.image.BufferedImage import javax.imageio.ImageIO import java.io.File /** * 高级类型 */ //单例类型,链式调用 class Document{ def setTitle(title:String)={this}

从零学scala(九)类型参数、高级类型

一:类型参数 泛型类 //泛型类,基本和java是一致的          class Pair[T,S](val first:T,val second:S) val pair1 = new Pair("42",42)          val pair2 = new Pair[Any,Any](42,"42") 泛型函数 //返回数组中间的值          def getMiddle[T](a:Array[T]) = a(a.length/2) def mai

TypeScript高级类型

交叉类型(Intersection Types) 交叉类型是将多个类型合并为一个类型. 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性. 例如,Person & Serializable & Loggable同时是Person和Serializable和Loggable. 就是说这个类型的对象同时拥有了这三种类型的成员. 我们大多是在混入(mixins)或其它不适合典型面向对象模型的地方看到交叉类型的使用. (在JavaScript里发生这种情况的场合很多