Loading

TypeScript 初体验

什么是 TypeScript

TypeScript 是静态类型的 JavaScript 超集

类型系统按照「类型检查的时机」来分类,可以分为动态类型和静态类型。

动态类型是指在运行时才会进行类型检查,这种语言的类型错误往往会导致运行时错误。

TypeScript 在运行前需要先编译为 JavaScript,而在编译阶段就会进行类型检查,所以 TypeScript 是静态类型。

TypeScript 是弱类型的 JavaScript 超集

类型系统按照来分类,可以分为强类型和弱类型。

TypeScript 是完全兼容 JavaScript 的,它不会修改 JavaScript 运行时的特性,所以它们都是弱类型

但可以借助 TypeScript 提供的类型系统,以及 ES-Lint 提供的代码检查功能,来限制变量必须为某一种类型。

也就是说没有绝对的强弱类型之分,依据使用的情况不同,提现不同的类型偏向。

作为对比,Python 是强类型。

TypeScript 的特点

主要特点

  • 「省心的类型推断」TS 根据变量初始赋值,推导出的值的类型,大部分类型都不需要手动声明了。
  • 「灵活的类型标准」通过在tsconfig.json文件中自定义编译选项,从而降低或增加类型检查的标准。
  • 「优秀的的 IDE 能力」TS 增强了编辑器代码补全、接口提示、跳转到定义、代码重构等功能。
  • 「愈加完善的的生态」对于一些不支持 TS 的第三方库,可以通过安装社区维护的「类型声明库」来实现该库的代码补全、接口提示等能力。

项目迁移

TS 可以和 JS 共存。这就意味着能够把旧的 JS 代码逐步迁移成 TS 代码。对于哪些迁移成本过高的 JS 代码,可以通过「类型声明文件」实现旧项目的渐进式迁移。

启用自动编译

先配置好tsconfig.json。打开命令行菜单,选择运行任务,选择tsc:watch-tsconfig.json监听 TS 文件更改。

注意事项

TS 编译阶段仅在语法层面进行类型检查,如果是要在程序运行阶段也需要类型限制,那就要手动进行类型检查,比如判断运行时请求接口拉取的数据类型。

即使在编译阶段报错,TS 仍然能够编译出 JS 文件。如果需要禁止报错的 TS 文件编译成 JS 文件,可以在tsconfig.json 中配置 noEmitOnError 即可。

基础类型

空值 void

在 JS 中没有void空值的概念,在 TypeScript 中,可以用 void 表示没有任何返回值的函数:

function alertName(): void {
    alert('My name is Tom');
}

声明一个 void 类型的变量没有什么用,它只能被赋值为undefined, null

let name: void = undefined;

Null 和 Undefined

void 的区别是,undefinednull 是所有类型的子类型。也就是说 undefined 类型的变量,可以赋值给其他类型的变量。而 void 类型的变量不能赋值给其他类型的变量。

任意类型

当不知道数据类型时可以通过any类型对变量进行类型设置。比如不知道请求拉取服务器的数据类型时,就可以指定any类型完成数据的缓存。但更建议手动进行类型检查。

import { getList } from '@/api/goods'

getList()
  .then(res => {
  let result: any;
  if (res.code === 200) {
    result = res.data;
  }
    
  else {
    throw new Error(res.msg);
  }
})

可以认为声明一个any类型的变量就相当于用原生 JS 进行编程了,不再对变量指定一个基本或者引用类型,程序运行的过程中也能修改变量的值。

需要注意的是,变量如果在声明的时候,未指定其类型,那么它会被识别为任意值类型,这就是「类型推断」:

let something: any;// 等价于 let something;
something = 'seven';
something = 7;

something.setName('Tom');

联合类型

联合类型是指,变量、形参、返回值可以是两种或两种以上的类型。通过|分隔,类似于 JS 中的或运算符:

let num: string | number;
num = 'seven';
num = 7;
myFavoriteNumber = true;// 报错,不属于指定类型中的一种

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

function getStr(think: string | number | boolean): string {
  return think.toString();
}

联合类型的变量在被赋值的时候,会根据类型推断的规则推断出一个类型:

let myFavoriteNumber: string | number;
myFavoriteNumber = 'seven';// 类型推断 string
console.log(myFavoriteNumber.length); // 5
myFavoriteNumber = 7;// 类型推断为 number
console.log(myFavoriteNumber.length); // 编译时报错

接口对象声明

在 TypeScript 中,我们使用接口(Interfaces)来定义对象的类型。

TypeScript 中的接口是一个非常灵活的概念,除了可用于对类的一部分行为进行抽象以外,也常用于对「对象的形状(Shape)」进行描述。

换句话说,通过设置接口对象类型,来限制(约束)变量接收对象的形状,也能迅速描述出该接口的形状

举一个例子:

interface Cat {
  name: string,
  color: string
}

// 如果多写或少写【未定义的属性】都会类型检查报错,变量和接口的【形状】必须一致。
let miaomi: Cat = {
  name: '大黄',
  color: '狸花'
}

接口可选属性:

interface Cat {
  name: string,
  color?: string
}

// 忽略可选属性
let miaomi: Cat = {
  name: '大黄',
}

// 添加未定义属性,类型检查会报错
let miaomi: Cat = {
  name: '大黄',
  gender: 'male'
}

接口任意属性:

// 通过 [propName: string]: any 添加任意类型的属性
interface Cat {
  name: string,
  color?: string,
  [propName: string]: any// 类型也可以指定非 any
}

let miaomi: Cat = {
  name: '大黄',
  gender: 'male',
  age: 1
}

需要注意的是,一旦定义了任意属性,那么确定属性和可选属性的类型都必须是它的类型的子集

一个接口中只能定义一个任意属性。如果接口中有多个类型的属性,则可以在任意属性中使用联合类型:

interface Cat {
  name: string,
  color?: string,
  [propName: string]: string | number
}

let miaomi: Cat = {
  name: '大黄',
  gender: 'male',
  age: 1
}

有时候我们希望对象中的一些字段只能在创建的时候被赋值,那么可以用 readonly 定义只读属性:

interface Cat {
  readonly id: number,
  name: string,
  color?: string,
  [propName: string]: string | number
}

let miaomi: Cat = {
  id: 001,
  name: '大黄',
  gender: 'male',
  age: 1
}

miaomi.id = 002;// 只读属性,不可重新赋值

数组类型

普通声明:

let arr1: number[];// 普通声明, 中括号前指定数组内元素的类型
let arr2: any[];// 数组元素为任意类型
arr1 = null;
arr1 = undefined;
arr1 = Array.from(new Set([1, 2, 3, 3, 3]));

泛型声明:

let arr2: Array<object> = [{}];// 泛型声明, 尖括号内指定数组内元素的类型
arr1 = null;
arr1 = undefined;

接口数组声明:

interface IMyArr {
  [index: number]: number;// 元素类型也可以是任意类型和联合类型
}

let arr: IMyArr = [1, 2, 3]

不常用,用于表示类数组适用。

函数类型

接口声明函数类型:

interface IMyFunc {
  (a: string, b: string): boolean
}

let isContain: IMyFunc = function(a: string, b: string): boolean {
 return a.indexOf(b) >= 0;
};

let result: boolean = isContain('123', '1');

console.log(result);

命名声明函数类型:

function isContain(a:string, b:string): boolean {
  return  a.indexOf(b) >= 0;
 }
 
 let result: boolean = isContain('123', '1');
 
 console.log(result);

表达式声明函数类型(过于繁琐,不建议):

const isContain: (a:string, b:string) => boolean = function (a:string, b:string): boolean {
  return  a.indexOf(b) >= 0;
 }
 
let result: boolean = isContain('123', '1');
 
console.log(result);

可选参数和默认参数:

?添加到参数名前表示可选参数。

// 默认参数不传就是 undefined
function sum(a: number, b: number, c?: number): number {
  return a+b+c
}

sum(1, 2);

必须注意,必选参数不能写在可选参数后面因为必选参数写在可选参数后面的话,仅两个参数时无法去判断第二个参数是必选参数还是可选参数。

函数重载:

当函数需要根据不同的参数类型(联合类型或者any类型),进行不同的运算或者操作时,就可以利用重复函数名但设置不同参数类型启用【函数重载】。

function add(a: number, b: number): number {
  return a+b
}

function add(a: string, b: string): string {
  return a+b
}

add(1, 2)
add('张', '三')

TS 函数重载就是不同类型约束版本的同一个函数,根据输入类型判断适用哪个函数进行重载。

剩余参数:

在 TS 中使用剩余参数也需要为剩余参数指定类型,比如...args: string[]就是指定所有剩余参数必须是字符串。

function printArgs(a: number, b: number, ...args: string[]):  void {
  console.log(args);
}

printArgs(1, 2, 'a', 'b', 'c');

类型断言:

在还不确定类型的时候就访问其中一个类型特有的属性或方法,用【类型断言】指定一个值的类型。然后根据确定的参数类型进行判断或者其他操作。

function showToast(toast: string | boolean = false): void {
  if ((toast as  string).length) {
    console.log('提示信息' + toast);
  }

  else if ((<boolean>toast).toString == 'true') {
     console.log('已开启提示信息');
  }
}

由于各种原因,在 TS 中断言最好用值 as 类型的形式表示

断言为any类型可以访问任意类型的属性和方法

posted @ 2023-05-15 09:09  mx羽林  阅读(20)  评论(0编辑  收藏  举报