TypeScript高级类型

更新日期: 2020-02-20阅读: 2k标签: 类型

前言

TypeScript真香系列的内容将参考中文文档,但是文中的例子基本不会和文档中的例子重复,对于一些地方也会深入研究。另外,文中一些例子的结果都是在代码没有错误后编译为JavaScript得到的。如果想实际看看TypeScript编译为JavaScript的代码,可以访问TypeScript的在线编译地址,动手操作,印象更加深刻。

交叉类型

交叉类型是将多个类型合并为一个类型,相当于一种并的操作。

interface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}

let animal: IDog & ICat;
animal = {
    name: "哈士奇",
    age: 1,
    color: "white",
}
animal.name; // "哈士奇"
animal.age;  // 1

上面animal中的属性一个都不能少,如果少了属性的话,就会出现下面的错误:
i

nterface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}

let animal: IDog & ICat;
animal = {   //错误,color属性在ICat中是必须的
    name: "哈士奇",
    age: 1,
    // color: "white",
}

联合类型

联合类型可以说是和交叉类型相反,声明的类型不确定,可以是多个类型中的一个或几个。

let a: number | string;
a = 1;
a = "s";
a = false; // 错误,类型false不能分配给类型 number | string

看一个和交叉类型相对应的例子:

interface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}

let animal: IDog | ICat; // 这里我们把&改成了|
animal = {     //没有报错
    name: "哈士奇",
    age: 1,
    // color: "white",
}
animal.name;
animal.age;

再看一个例子:

interface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}

let animal: IDog | ICat;
animal = {
    name: "哈士奇",
    age: 1,
    color: "white",
}
animal.name;
animal.age; //错误,age不存在于ICat. age不存在于IDog | ICat

我们可以看见上面的例子出现了错误,这是因为TypeScript编译器age不知道是IDog还是ICat,所以只能访问公共的name属性。如果我们想要访问这个属性的话,该怎么办?我们可以使用类型断言:

interface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}

let animal: IDog | ICat;
animal = {
    name: "哈士奇",
    age: 1,
    color: "white",
}
animal.name;
(<IDog>animal).age; // 1

这下就能访问age属性了。

类型保护

有时候我们会遇到类似于下面这种场景:

interface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}

function animal(arg: IDog | ICat): any {
    if (arg.color) {     //错误
        return arg.color  //错误
    }
}

但是上面的代码会出现错误。如果想要上面这段代码正常工作,可以和联合类型中的例子一样,使用类型断言:

interface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}

function animal(arg: IDog | ICat):any {
    if ((<ICat>arg).color) {
        return (<ICat>arg).color;
    }
}

除了类型断言,我们还可以利用类型保护来进行判断,常用的类型保护有三种:typeof类型保护,instanceof类型保护和自定义类型保护。

typeof类型保护

function animal(arg: number | string): any {
    if (typeof arg === "string") {
        return arg + "猫";
    }
}

typeof类型保护只有两种形式能被识别: typeof v === "typename"和 typeof v !== "typename"。"typename"必须是 "number", "string", "boolean"或 "symbol"。

instanceof类型保护

class Dog {
    name: string;
    age: number;
    constructor() { 

    };
}
class Cat {
    name: string;
    color: string;
    constructor() { 

    };
}

let animal: Dog | Cat = new Dog();
if (animal instanceof Dog) {
    animal.name = "dog";
    animal.age = 6;
}
if (animal instanceof Cat) {
    animal.name = "cat";
    animal.color = "white";
}
console.log(animal); //Dog {name: "dog", age: 6}

instanceof的右侧要求是一个构造函数,TypeScript将细化为:

  1. 此构造函数的 prototype属性的类型,如果它的类型不为 any的话;
  2. 构造签名所返回的类型的联合。

自定义类型保护

对于一些复杂的情况,我们可以自定义来进行类型保护:

interface IDog { 
    name: string,
    age: number,
}
interface ICat {
    name: string,
    color: string
}
let animal: IDog | ICat;
animal = {
    name: "哈士奇",
    age: 6,
}
function isDog(arg: IDog | ICat): arg is IDog {

    return arg !== undefined;
    
}
if (isDog(animal)) {
    console.log(animal.age);  //6
}

类型别名

类型别名可以给类型取一个别名。类型别名和接口类似,但又有不同。

type Name = number;
type Types = number | string;
type NAndT = Name & Types;
type MyFunc = () => number;

function animal(arg: Types) { 
    return arg;
}
animal("哈士奇"); //"哈士奇"

类型别名可以作用于原始类型、联合类型、泛型等等。

type Dog<T> = { value: T };

function dog(arg: Dog<string>) {
  return arg;
}

dog({ value: "哈士奇" }); //{value: "哈士奇"}
dog({ value: 1});  //错误,类型number不能分配给string
dog("哈士奇");  //错误,参数“哈士奇”不能分配给类型 Dog<string>

接口和类型别名的区别

区别一:接口可以创建新的名字,而且可以在其它任何地方使用;类型别名不创建新的名字,而是起一个别名。
区别二:类型别名可以进行联合,交叉等操作。
区别三:接口可以被extends和implements以及声明合并等,而类型别名不可以。

这里介绍一下声明合并

“声明合并”是指编译器将针对同一个名字的两个独立声明合并为单一声明。 合并后的声明同时拥有原先两个声明的特性。
任何数量的声明都可被合并;不局限于两个声明。

举个例子:

interface IDog {
    name: string;
    setName(arg:string): string;
}

interface IDog { 
    age: number;
}

interface IDog { 
    color: string;
}
let dog: IDog;
dog = {
    color: "black",
    age: 6,
    name: "哈士奇",
    setName: (arg) => { 
        return arg
    }
}

合并之后:

interface IDog { 
    color: string;
    age: number;
    name: string;
    setName(arg:string): string;
}

我们可以看出,后面的接口在合并后出现在了靠前的位置。

字符串字面量类型和数字字面量类型

字符串字面量允许我们指定字符串为必须的固定值。

type Dog = "哈士奇" | "泰迪" | "中华田园犬" | "萨摩耶";

function dog(arg: Dog):any { 
  switch (arg) {
    case "哈士奇":
      return "傻狗";
    case "泰迪":
      return "精力旺盛";
    case "中华田园犬":
      return "忠诚";
    case "萨摩耶":
      return "微笑天使";
  }
}
dog("哈士奇");  //"傻狗"
dog("柯基");  //错误,参数"柯基"不能分配给类型Dog

数字字面量同理。

type Num = 1 | 2 | 3 | 4 | 5 | 6 | 7;

function week(arg: Num):any {
  switch (arg) {
    case 1:
      return "星期一";
    case 2:
      return "星期二";
    case 3:
      return "星期三";
    case 4:
      return "星期四";
    case 5:
      return "星期五";
    case 6:
      return "星期六";
    case 7:
      return "星期日";
  }
}
week(6);  //"星期六"
week(8);  //错误

可辨识联合

我们可以合并单例类型、联合类型、类型保护和类型别名来创建一个叫做可辨识联合的高级模式。它具有三个要素:

  1. 有普通的单例类型属性— 可辨识的特征。
  2. 一个类型别名包含了那些类型的联合— 联合。
  3. 此属性上的类型保护。

可以看看下面这个例子就能理解了:

//我们首先声明了将要联合的接口,目前各个接口之间是没有联系的,
//只是都有一个kind的属性(可以称为可辨识特征或标签)但是有不同的字符串字面量类型
interface IColor {
  kind: "color";
  value: string;
}

interface ISize {
  kind: "size";
  height: number;
  width: number;
}

//然后我们利用类型别名和联合类型把两个接口联合到一起
type MyType = IColor | ISize;

//最后使用可辨识联合
function types(arg: MyType) :any{
  switch (arg.kind) {
    case "color":
      return arg.value;
    case "size":
      return arg.height * arg.width;
  }
}
types({ kind: "color", value: "blue" });  //"blue"
types({ kind: "size", height: 10, width: 20 }); //200

索引类型

使用索引类型,编译器就能够检查使用动态属性名的代码。我们首先要知道两个操作符。

  1. 索引类型的查询操作符 keyof T,意为:对于任何类型T,keyof T的结果为T上已知公共属性的联合;
  2. 索引访问操作符T[K]。
interface IDog{
  a: string,
  b: number,
  c: boolean,
}

let dog: keyof IDog; //let dog: "a" | "b" | "c"
let arg: IDog["a"];  //let arg: string

再看一个较为复杂的例子:

interface IDog{
  name: string,
  age: number,
  value: string,
}

let dog: IDog;
dog = {
  name: "二哈",
  age: 6,
  value: "奥里给"
}

function myDog<T, K extends keyof T>(x: T, args: K[]): T[K][] { 
  return args.map(i => x[i]);
}

myDog(dog, ['name']);  //["二哈"]
myDog(dog, ['name', 'value']);  //["二哈", "奥里给"]
myDog(dog, ['key']);  //错误,类型不匹配

映射类型

有时候我们可以会遇到这种情况,把每个成员都变为可选或者只读:

interface Person{
    name: string;
    agent: number;
}

interface PersonPartial {
    name?: string;
    age?: number;
}

interface PersonReadonly {
    readonly name: string;
    readonly age: number;
}

这在JavaScript里经常出现,TypeScript提供了从旧类型中创建新类型的一种方式 — 映射类型。 在映射类型里,新类型以相同的形式去转换旧类型里每个属性。

interface IPerson{
    name: string;
    agent: number;
}

type OPartial<T> = {
    [P in keyof T]?: T[P];
}

type OReadonly<T> = {
    readonly [P in keyof T]: T[P];
}
//使用方式
type PersonPartial = OPartial<IPerson>;
type ReadonlyPerson = OReadonly<IPerson>;

原文:https://github.com/zhongsp/TypeScript/blob/master/handbook/advanced-types.md

链接: https://fly63.com/article/detial/7510

JS中Null与Undefined的区别

在JavaScript中存在这样两种原始类型:Null与Undefined。这两种类型常常会使JavaScript的开发人员产生疑惑,在什么时候是Null,什么时候又是Undefined?Undefined类型只有一个值,即undefined。当声明的变量还未被初始化时,变量的默认值为undefined。

Javascript的类型检测

主要介绍了JS中检测数据类型的几种方式,typeof运算符用于判断对象的类型,但是对于一些创建的对象,它们都会返回\'object\',有时我们需要判断该实例是否为某个对象的实例,那么这个时候需要用到instanceof运算符

js类型转换的各种玩法

对于object和number、string、boolean之间的转换关系,ToPrimitive是指转换为js内部的原始值,如果是非原始值则转为原始值,调用valueOf()和toString()来实现。

JavaScript类型:关于类型,有哪些你不知道的细节?

Undefined类型表示未定义,它的类型只有一个值为undefined。undefined和null有一定的表意差别。非整数的Number类型无法使用 == 或 === 来比较,因为 JS 是弱类型语言,所以类型转换发生非常频繁

为你的 JavaScript 项目添加智能提示和类型检查

近在做项目代码重构,其中有一个要求是为代码添加智能提示和类型检查。智能提示,英文为 IntelliSense,能为开发者提供代码智能补全、悬浮提示、跳转定义等功能,帮助其正确并且快速完成编码。

js的类型

基本类型:按值访问,可以操作保存在变量中实际的值;引用类型数据存在堆内存,而引用存在栈区,也就是说引用类型同时保存在栈区和堆区,关于==的执行机制,ECMASript有规范,因为==前后的值交换顺序,返回的值也是一样的,所以在此对规范做出如下总结

再也不用担心 JavaScript 的数据类型转换了

JavaScript 是一种弱类型或者说动态类型语言。所以你不用提前声明变量的类型,在程序运行时,类型会被自动确定,你也可以使用同一个变量保存不同类型的数据。

JavaScript基础之值传递和引用传递

js的值传递和引用(地址)传递:js的5种基本数据类型 number,string,null,undefined,boolean 在赋值传递时是值传递,js的引用数据类型(object,array,function)进行引用传递,其实底层都是对象。

JS中的布尔 数字 字符串

JS中所有的值都可以转换成布尔类型 使用Boolean()或者 !!(两个感叹号),JS中所有的值都可以转换成数字类型,使用Number()或+。数字类型转换场景目的只有一个,用于计算,将后台传递的数据,从字符串转换为数字并参与计算

if条件中,js的强制类型转换

众所周知,JS在很多情况下会进行强制类型转换,其中,最常见两种是:1.使用非严格相等进行比较,对==左边的值进行类型转换2.在if判断时,括号内的值进行类型转换,转化为布尔值

点击更多...

内容以共享、参考、研究为目的,不存在任何商业目的。其版权属原作者所有,如有侵权或违规,请与小编联系!情况属实本人将予以删除!