TypeScript入门 | 青训营笔记

63 阅读6分钟

这是我参与「第四届青训营 」笔记创作活动的的第12天

一. 为什么选择typescript

1. 发展历史

  • 2012-10:微软发布了TypeScript第一个版本(0.8)
  • 2014-10:Angular发布了基于TypeScript的2.0版本
  • 2015-04:微软发布了 Visual Studio Codes
  • 2016-05:@types/react 发布,TypeScript可开发React
  • 2020-09:Vue 发布了3.0版本,官方支持 TypeScript
  • 2021-11:v4.5版本发布

2. JavaScript与TypeScript的爱恨情仇

  1. 定义
  • 动态类型:执行时才确定数据类型。执行时做类型匹配。
  • 静态类型:在编译时变量的数据类型即可确定的语言。
  • 弱类型语言:类型转换。弱类型定义的语言,某一个变量被定义类型,该变量可以根据环境变化自动。
  • 强类型语言:强类型语言不会隐形进行语言类型转变。
  1. 分类
  • JavaScript:动态类型,弱类型语言。
  • TypeScript:静态类型,弱类型语言。
  1. TypeScript
  • 静态类型:

  • 可读性增强:基于语法解析TSDoc,ide增强

  • 可维护性增强:在编译阶段暴露大部分错误=>多人合作的大型项目中,获得更好的稳定性和开发效率

  • JS的超集:

  • 包含于兼容所有Js特性,支持共存

  • 支持渐进式引入与升级

  1. 编译器推荐:VSCode TS官网在线编辑器

二. TS基本语法

1. 基础数据类型

js:定义声明 变量名 = 变量值;

ts:定义声明 变量名:变量类型 = 变量值;

let q: string = 'aaa';  // 字符串
let w: number = 1;  // 数字
let e: boolean = true;  // 布尔值
let r: null = null;  // null
let t: undefined = undefined;  // undefined

2. 对象类型

interface接口:大写字母I开头,与其他区分。

只读属性:实现接口后,值可以获取到,但是无法修改。 readonly 属性名:属性类型。

可选属性:实现接口时,可以选择实现,也可以选择不实现。属性名?:属性值类型。

任意属性:实现接口时,可以添加零个,一个或多个属性。[ 属性名:属性类型 ]:属性值类型。

const bytedancer: IBytedancer = {
  id: 1234,
  name: 'syz',
  sex: 'female',
  age: 18,
  hobby: 'swimming'
}

interface IBytedancer {
  readonly id: number; // 只读属性:约束属性不可在对象初始化外赋值
  name: string;
  sex: 'man' | 'woman' | 'other';
  age: number;
  hobby?: string; // 可选属性:定义该属性可以不存在
  [key: string]: any; //任意属性:约束所有对象属性都必须是该属性的子类型
}

3. 函数类型

typescript 中的函数需要对参数和返回值进行类型声明。

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

const mult: (x: number, y: number) => number = (x, y) => x * y;

4. 函数重载

重载:同一函数名,具有不同参数。根据传入参数类型进行

/*对getDate函数进行重载,timestamp为可缺省参数*/
function getDate(type: 'string ' , timestamp?: string):string;
function getDate(type: 'date', timestamp?: string): Date;
function getDate(type: 'string' | 'date', timestamp?: string): Date | string {
	const date = new Date(timestamp);
	return type == 'string'? date.toLocalString() : date;
}
const x = getDate('date'); // x: Date
const y = getDate('string', '2018-01-10'); // y: string

5. 数组类型

/* 方括号表示 */
type IArr1 = number[];
/* 泛型表示 */
type IArr2 = Array<string | number | Record<string, number>>;
/* 元组表示 */
type IArr3 = [number, boolean, string];
/* 接口表示 */
interface IArr4 {
  [key: number]: any;
}

const arr1: IArr1 = [1, 2, 3, 4, 5];
const arr2: IArr2 = [1, 2, '3', '4', {a: 1}];
const arr3: IArr3 = [1, true, 'a'];
const arr4: IArr4 = ['a', () => null, {}, []];

6. Typescript补充类型与泛型

当我们定义一个变量不确定类型的时候有两种解决方式:

  • 使用 any: 使用 any 时存在的问题,虽然知道传入值的类型但是无法获取函数返回值的类型,另外也失去了类型保护的优势。
  • 使用泛型: 泛型指的是在定义函数/接口/类型时,不预先指定具体的类型,而是在使用的时候在指定类型限制的一种特性。
/* 空类型,表示无赋值 */
type IEmptyFunction = () => void;
/* 任意类型,是所有类型的子类型 */
type IAnyType = any;
/* 枚举类型:支持枚举值到枚举名的正、反向映射 */
enum EnumExample {
  add = '+',
  mult = '*'
}
EnumExample['add'] === '+';
EnumExample['+'] === 'add';

enum ECorlor {Mon, Tue, Wed, Thu, Fri, Sat, Sun};
ECorlor['Mon'] === 0;
ECorlor[0] === 'Mon';
/* 泛型:不预先指定具体的类型,而在使用的时候再指定类型的一种特性 */
type IGetRepeatArrR = <T>(target: T) => T[];

7. 类型别名&类型断言

ts 类型断言可以用来手动指定一个值的类型。类型断言只是欺骗编译器,让编译器可以编译通过,但是如果进行强制类型转换,在执行的过程中会报错。

断言的语法分为两种:<> 尖括号;as语法。

/*通过type关键字定义了I0bjArr的别名类型*/
type IObjArr = Array<{
	key: string;
	[objKey: string] : any;
}>
function keyBy<T extends I0bjArr>(objArr: Array<T>){
	/*未指定类型时,result类型为{}*/
	const result = objArr.reduce((res, val,key) => {
		res [key] = val;
		return res;
	},{});
	/*通过as关键字,断言result类型为正确类型*/
	return result as Record<string,T>;
}

8. 字符串/数字 字面量

/*允许指定字符串/数字必须的固定值*/

/* IDomTag必须为html、body、div、span中的其一*/
type IDomTag = 'html' | 'body' | 'div' | 'span';

/* IOddNumber必须为1、3、5、7、9中的其一*/
type IOddNumber = 1 | 3 | 5  |7 | 9;

三. TS高级类型

1. 联合/交叉类型

联合/交叉类型解决了类型声明繁琐,存在较多重复的问题。

  • 联合类型:IA | IB; 联合类型表示一个值可以是几种类型之一
  • 交叉类型:IA & IB; 多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
Const bookList=[
	{
		author: 'xiaoming',
		type:'history',
		range: '2001-2021',
	},
	{
		author: 'xiaoli',
		type: 'story',
		theme: 'love',
	}
]

type IBookList = Array<{
	author: string;
} & ({
	type: 'history';
	range: string;
} | {
	type: 'story';
	theme: string;
})>

2. 类型保护与类型守卫

访问联合类型时,出于程序安全,仅能访问联合类型中的交集部分。

类型守卫:定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域。

interface IA {a: 1, a1: 2 }
interface IB {b: 1, b1: 2 }

function log( arg: IA | IB) {
	/* 报错: 类型“IA|IB”上不存在属性“a”。类型“IB”上不存在属性“a”。 */
	/* 结论: 访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分 */
		if (arg.a) {
			console.log(arg.a1);
		} else {
			console.log(arg.b1);
		}
}

/* 类型守卫:定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域 */
function getIsIA(arg: IA | IB): arg is IA{
	return !!(arg as IA).a;
}
function log2(arg: IA | IB){
	if(getIsIA(arg)){
		console.log(arg.a1);
	} else {
		console.log(arg.b1);
	}
}

3. 索引类型

interface IMerge {
 <T extends Record<string,any>>(sourceObj: Partial<T>,target0bj: T): T;}
type IPartial<T extends Record<stringany>> = {
	[P in keyof T]?: T[P];
}
// 索引类型:关键字【keyof】,其相当于取值对象中的所有key组成的字符串字面量,如
type IKeys = keyof { a: string; b: number }; // =>type IKeys = "a" | "b"
//关键字【in】,其相当于取值字符串字面量中的一种可能,配合泛型P,即表示每个key
//关键字【?】,通过设定对象可选选项,即可自动推导出子集类型

4. 函数返回值类型

// 实现函数delayCall的类型声明
// delayCall接受一个函数作为入参,其实现延迟1s运行函数
// 其返回promise,结果为入参函数的返回结果

function delayCall(func){
	return new Promise( resolve => {
		setTimeout(() => {
			const result = func();
			resolve(result);
		}, 1000) ;
	});
}

四. 工程应用

1. 浏览器web

webpack

  1. 配置webpack loader相关配置
  2. 配置tsconfig.js文件
  3. 运行webpack启动/打包
  4. loader处理ts文件时,会进行编译与类型检查

相关loader

  1. awesome-typescript-loader
  2. babel-loader

2. node.js

使用TSC编译

  1. 安装Node与npm
  2. 配置tsconfig.js文件
  3. 使用npm安装tsc
  4. 使用tsc运行编译得到js文件