TypeScript讲解-第二弹

200 阅读6分钟

类型推论

如果没有明确指定变量的类型,那么TypeScript就会自动去其推断变量的类型。

注意:定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查

let myFavoriteNumber = "seven";
// 提示你:不能将类型“number”分配给类型“string”
myFavoriteNumber = 7;

联合类型

联合类型表示你可以给变量定义多种类型,使用 | 分隔符来进行分割

// 联合类型 (表示age有可能是number也有可能是string类型的)
const age: number | string = 20;

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法

function getLength(something: string | number): string {
  // TS会提示你 类型“string | number”上不存在属性“length”。类型“number”上不存在属性“length”
  return something.length;
  // 此时我们只能访问到 .toLocaleString()  .toString()  .valueOf() 方法,因为这在string 和 number都存在
}

联合类型的变量在被赋值的时候,会根据类型推论的规则自动推断出其类型

const favoriteNumber: string | number = 78.45678;
// 自动推断出为联合类型的 number 类型 
console.log(favoriteNumber.toFixed(2));
// 那么之后去调用 number 没有的方法或属性就会报错
console.log(favoriteNumber.length);

函数

一个函数有输入或输出,在TS中对其输入和输出进行类型上的约束,需要把输入和输出都考虑到

为函数定义类型方式如下:

写法如下:

function getSumNumber(x: number, y: number): number {
  return x + y;
}

函数表达式(匿名函数)定义类型写法如下

let getSumFunction = function(x: number, y:number): number {
  return x + y;
}

上面这个是可以通过编译的,不过事实上,上面的代码只对等号右侧的匿名函数进行了类型定义,而等号左边的 getSumFunction是通过赋值操作进行类型推论而推断出来的。如果需要我们手动给 getSumFunction 添加类型,则应该是 如下这样的写法:

let getSumFunction: (x: number, y: number) => number = function(x: number, y:number): number {
  return x + y;
}

这个代码表示getSumFunction 是这个类型 (x: number, y: number) => number, 所以后面写的匿名函数必须按照这个类型的规范去写。

可选参数和默认参数

可选参数注意点如下:

  1. 在TypeScript里的每个函数参数都是必须的
  2. 在JavaScript里,每个参数都是可选的,可传可不传。 没传参的时候,它的值就是undefined
  3. 在TypeScript里我们可以在参数名旁使用?实现可选参数的功能
  4. 可选参数必须在必选参数的后面
function buildName(firstName: string, lastName?: string): string {
  if (lastName) {
    return firstName + "-" + lastName;
  }
  return firstName;
}

默认参数注意点如下:

  1. 与可选参数不同的是,带默认值的参数不需要放在必须参数的后面。如果带默认值的参数出现在必须参数前面,用户必须明确的传入 undefined值来获得默认值
function buildName(firstName: string, lastName: string = "Cat"): string {
  if (lastName) {
    return firstName + " " + lastName;
  }
  return firstName;
}

剩余函数

有时,你想同时操作多个参数,或者你并不知道会有多少参数传递进来。在JS里可以使用 arguments来访问所有传入的参数。而在TypeScript里,我们可以把所有参数收集到一个变量里

剩余参数会被当做个数不限的可选参数。 可以一个都没有,同样也可以有任意个。 编译器创建参数数组,名字是你在省略号( ...)后面给定的名字,你可以在函数体内使用这个数组。

function buildName(firstName: string, ...restOfName: string[]) {
  console.log(restOfName);
  // [ 'Samuel', 'Lucas', 'MacKinzie' ]
  return firstName + " " + restOfName.join(" ");
}

let employeeName = buildName("Joseph", "Samuel", "Lucas", "MacKinzie");
console.log(employeeName);
// Joseph Samuel Lucas MacKinzie

接口

在 TypeScript 中,可以使用接口来 定义对象中属性的各个类型。 在 TypeScript 里接口的作用就是为这些类型命名和为代码或第三方代码定义契约。

简单的Demo

// IPerson接口
interface IPerson {
  username: string;
  age: number;
  sex: number;
}

// 对象定义为是一个IPerson的类型,就必须实现接口当中的属性
const Tom: IPerson = {
  username: "Tom",
  age: 20,
  sex: 1,
};

可选属性

接口里的属性不全都是必需的,定义方式如下:

interface IPerson {
  name: string;
  age: number;
  sex?: number; // 可选属性
}
// 这时我们 有一个 对象 需要去实现 IPerson 这个接口
const tom: IPerson = {
  name: "Tom",
  age: 21,
};

只读属性

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性。之后去修改只读属性就会报错。

interface IPerson {
  readonly id: number; // 只读属性
  name: string;
  age: number;
}
// 这时我们 有一个 对象 需要去实现 IPerson 这个接口
const tom: IPerson = {
  id: 20211027,
  name: "Tom",
  age: 21
};

任意属性

有时候我们希望一个接口允许有任意的属性,可以使用如下方式:

prop 被定义为 string 类型,可以返回 any 类型,此时在实现该接口时 在定义 name、age属性、sex属性为可选属性之后,用户可以随意定义接口中没有的属性。

interface IPerson {
  name: string;
  age: number;
  sex?: number; // 可选属性
  [prop: string]: any; // 任意属性
}
// 这时我们 有一个 对象 需要去实现 IPerson 这个接口
const tom: IPerson = {
  name: "Tom",
  age: 21,
  gender: "male",
};

接口描述函数(函数类型)

接口能够描述JavaScript中对象拥有的各种各样的外形。 除了描述带有属性的普通对象外,接口也可以描述函数类型。

// 定义ISearchFunc接口
interface ISearchFunc {
  (source: string, subString: string): boolean;
}

// 定义一个函数实现这个接口
let mySearch: ISearchFunc = function (source: string, subString: string) {
  let result = source.search(subString);
  if (result == -1) {
    return false;
  }
  return true;
};

注意点:

  1. 函数的参数名不需要与接口里定义的名字相匹配,可以不匹配的。

接口描述数组

interface IStringArray {
  [index: number]: string
}

let myArray: IStringArray = ["Bob", "Fred"];

接口描述类(类类型)

通过接口也可以实现对类的属性或方法进行某种契约

interface ClockInterface {
	currentTime: Date;   // 定义变量
        setTime(d: Date);    // 定义方法
}
class Clock implements ClockInterface {
    currentTime: Date;
    setTime(d: Date) {
        this.currentTime = d;
    }
    constructor(h: number, m: number) {}
}

扩展接口

和类一样,接口也可以相互扩展相互继承的。 这让我们能够从一个接口里复制成员到另一个接口里,可以更灵活地将接口分割到可重用的模块里。

interface Shape {
  color: string;
}

interface Square extends Shape {
  sideLength: number;
}

let square: Square = {
  color: "blue",
  sideLength: 20
} 

数组类型定义表示方式

类型[] 表示法

// 类型[] 表示法
const numberArray: number[] = [1, 3, 5, 7, 9];

数组泛型

// 数组泛型
const numberArray2: Array<number> = [1, 3, 5, 7];

用接口描述数组

INumberArray 表示:只要索引的类型是数字时,那么值的类型必须是数字。

虽然接口也可以用来描述数组,但是我们一般不会这么做,因为这种方式比前两种方式复杂多了。

// 用接口表示数组
interface INumberArray {
  [index: number]: number;
}
const numberArray3: INumberArray = [1, 3, 5, 7, 9, 11];