TypeScript 入门 | 青训营笔记

84 阅读9分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第9天

TypeScript 是 JavaScript 的语法超集,它添加了静态类型,近几年的发展中,也受到诸多开发者的喜爱。Typescript 的社区逐渐壮大,日趋完善,为越来越多前端开发者提供服务,本节课将主要为同学解读 TypeScript 的优势及其主要使用的工具。

课堂笔记

视频地址

  1. TypeScript 的发展与基本语法 - 掘金 (juejin.cn)
  2. TypeScript 高级数据类型 - 掘金 (juejin.cn)

ppt地址 ‌⁡⁤​​⁤​⁡⁣‍​‬⁢‬​‌​⁤​⁢​⁡‌⁢⁣⁢‬​⁡⁢⁢‬‌⁡⁢‍⁣⁣‬⁢⁤⁡​​‬⁡​‬TypeScript入门.pptx - 飞书云文档 (feishu.cn)

课前

学习 javascript 基础语法

【可选】安装 typescript 开发工具

一、本堂课重点内容:

  1. Typescript 发展历史及优势解读
  2. Typescript 基础语法,包括类型、泛型、类型别名、类型字面量等
  3. Typescript 高级类型讲解及实例
  4. Typescript 工程应用介绍

二、详细知识点介绍:

TypeScript 的发展历史

image.png

TypeScript 和JavaScript的区别

TypeScript是微软开发的一个开源的编程语言,通过在JavaScript的基础上添加静态类型定义构建而成
JavaScript: 动态类型 (变量类型在运行时确定)、弱类型语言(数据类型能够被忽略的语言,变量的类型会根据环境变化自动转换,而强类型语言不经过强制转换是不会改变类型的,所以强类型语言不能进行字符串和数字的相加)
TypeScript: 静态类型(变量类型在编译时确定)、 弱类型语言

TypeScript 的优势:

1.静态类型的优势:
可读性增强:基于语法解析TSDoc,ide增强
可维护性增强:在编译阶段暴露大部分错误
=>多人合作的大型项目中,获得更好的稳定性和开发效率

2.JS的超集 优势:
包含于兼容所有js特性,支持共存
支持渐进式引入与升级

基本语法

基本数据类型

1. 布尔值    boolean
2. 数字     number
3. 字符串   string
4. 数组     Array
5. 元组     Tuple
6. 枚举     enum
7. 任意     any
8. Void                   表示没有任何类型
9. Null 和 Undefined      默认情况下null和undefined是所有类型的子类型
10. Never                 never类型表示的是那些永不存在的值的类型。

var/let/const 变量名:类型名称=赋值;

JavaScript 与 TypeScript

image.png

对象类型

//创建了一个bytedancer对象,类型为IBytedancer
const bytedancer: IBytedancer = {
    jobId: 9303245,
    name: 'Lin',
    sex: 'man',
    age: 28,
    hobby: 'swimming',
}
//自定义一个类型,约定在前面加上I表示一个类型,与对象进行区分
interface IBytedancer {
	/* 只读属性readonly:约束属性不可在对象初始化外赋值 */
	readonly jobId: number;
    name: string;
    sex: 'man' | 'woman' | 'other';
    age: number;
    /* 可选属性:定义该属性可以不存在 */
    hobby?: string;
    /* 任意属性:约束所有对象属性都必须是该属性的子类型 */
    [key: string]: any;
}

/* 报错:无法分配到"jobId",因为它是只读属性 */
bytedancer. jobId = 12345;
/* 成功:任意属性标注下可以添加任意属性 */
bytedancer .plateform = 'data';
/* 报错:缺少属性"name", 而hobby可缺省 */
const bytedancer2: IBytedancer = {
    jobId: 89757,
    sex: " woman ',
    age: 18,
}

函数类型

1.直接在函数上进行补充的两种方法
法一:
//function 函数名(参数:类型,参数:类型,...):返回值类型{}
function add(x: number, y: number): number {
	return x + y;
}
法二:
const mult: (x: number, y: number) => number = (x, y) => x * y;

2.使用接口表示函数类型
//定义一个接口类型,然后赋值给变量
interface IMult {
	(x: number, y: number): number;
}
const mult: IMult = (x, y) => x * y;

函数重载

/* 对getDate函数进行重载,timestamp为可缺省参数 */
function getDate(type: 'string', timestamp?: string): string;
interface IGetDate {
	(type : 'string', timestamp ?: string): string;
	(type : 'date', timestamp?: string): Date;
	(type: 'string' | 'date', timestamp?: string): Date | string;
}
/* 报错:不能将类型"(type: any, timestamp: any) => string | Date"分配给类型"IGetDate"。
	不能将类型"string | Date" 分配给类型"string"。
	不能将类型 "Date"分配给类型"string"。ts(2322) */
const getDate2: IGetDate = (type, timestamp) => {
	const date = new Date( timestamp) ; 
	return type === 'string' ? date.toLocaleString() : date;
}

此处会报错,应该让IGetDate这个函数的范围表达大于匿名函数的范围表达,改为(type: 'string' | 'date', timestamp?: any): Date | string;就可以通过了

数组类型

/* 「类型+方括号」表示 常见*/
type IArr1 = number[];
/* 泛型表示 尖括号中是数组的类型 常见*/ 
type IArr2 = Array<string | number| Record<string, number> > ;
/* 元组表示 定义数组每一项的类型*/
type IArr3 = [number, number, string, string];
/* 接口表示 */
interface IArr4 {
	[key: number]: any;
}

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

TypeScript补充类型

/* 空类型,表示无赋值 */
type IEmptyFunction = () => void;

/* 任意类型,是所有类型的子类型 */
type IAnyType = any;

/* 枚举类型:支持枚举值到枚举名的正、反向映射 */
//实现add和+ , mult和*  映射
enum EnumExample {
    add = '+',
	mult = '*',
}
EnumExample['add'] === '+';
EnumExample['+'] === 'add';
//实现索引的映射   
enum ECorlor { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
ECorlor['Mon'] === 0;
ECorlor[0] === 'Mon' ;
    
/*泛型*/
type INumArr = Array<number>;

泛型

不预先指定具体的类型,而在使用的时候再指定类型的一种特性,实际上就是变量指代

泛型在函数中的使用
//返回一个target类型的数组,长度为100
function getRepeatArr(target) {
	return new Array(100).fill(target); 
}
//数据类型为any,不能做到输入数字,返回数字的array;输入字符,返回字符的array
type IGetRepeatArr = (target: any) => any[];

//使用泛型,执行之前不知道数组的数据类型是什么,相当于定义了一个t的变量,表示数据的类型,根据传入的内容确定这个t
/* 泛型是不预先指定具体的类型,而在使用的时候再指定类型的一种特性 */
type IGetRepeatArrR = <T>(target: T) => T[];

在函数定义的括号前面使用<>来表示

泛型在接口、类、别名中的使用
/*泛型接口&多泛型*/
interface IX<T, U> {
	key: T;
	val: U;
}
/* 泛型类 */
class IMan<T> {
	instance: T;
}
/* 泛型别名 */
type ITypeArr<T> = Array<T>;`

类型名<T>

泛型约束

extends表示类型约束
=表示泛型参数默认类型

/* 泛型约束:限制泛型必须符合字符串 */
type IGetRepeatStringArr = <T extends string>(target: T) => T[];
const getStrArr: IGetRepeatStringArr = target => new Array(100).fill(target);
/* 报错:类型"number"的参数不能赋给类型“string"的参数 */
getStrArr(123);

/* 泛型参数默认类型 */
//因为没有传入类型参数给getRepeatArr,默认的还是number,当输入字符串时就会报错
type IGetRepeatArr<T = number> = (target: T) => T[];
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);
/* 报错:类型"string"的参数不能赋给类型“number"的参数 */
getRepeatArr('123');

类型别名 & 类型断言

类型别名

type关键字表示类型别名,相当于定义一个其他的名字

/*通过type关键字定义了IObjArr的别名类型*/
//此处的Array是个比较复杂的类型,定义别名IObjArr来表示,也可以起其他的名字
type IObjArr = Array<{
	key: string;
	[objKey:string]:any;
}>
类型断言
function keyBy<T extends IObjArr>(objArr: Array<T>) {
	/* 未指定类型时,result类型为{} */
	const result = objArr.reduce((res, val, key) => {
		res[key] = val;
		return res;
	}, {});
    /* 通过as关键字,断言result类型为正确类型 */
    return result as Record<string, T> ; 
}

字符串/数字 字面量

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

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

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

高级类型

联合/交叉类型
//历史书有时间范围range,故事书有主题theme
const bookList = [{	
    author: 'xiaoming',
    type: 'history',
    range: '2001 -2021',
}, {
    author: 'xiaoli',
    type: 'story',
    theme: 'love',
}]
//通过interface定义了两种不同的object
interface IHistoryBook {
    author: string;
    type: string;
    range: string
}
interface IStoryBook {
    author: string;
    type: string;
    theme: string;
}
//书列表可以是历史书也可以是故事书
type IBookList = Array<IHistoryBook | IStoryBook>;

有重复的地方,比较麻烦,可以进行改进
联合类型: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;
})>; 
类型保护与类型守卫

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

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);
    }
}

上述代码在js中不会报错,但是在ts中会报错,因为访问联合类型时,处于程序安全,仅能访问联合类型中的交集部分,可以通过类型守卫进行改造,额外定义一个getIsIA函数,用来判断传入的是否是IA

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

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

/*类型守卫:定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域 */
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);
    }
}

然而typescript是智能的,可以通过联合类型+类型保护=自动类型推断

// 实现函数logBook类型
// 函数接受书本类型,并logger出相关特征
function logBook(book: IBookItem) {
    // 联合类型+类型保护=自动类型推断
    if (book.type === 'history') {
        console.log(book.range)
    } else {
        console.log(book.theme);
    }
}
函数返回值类型
// 实现函数delayCall的类型声明
// delayCall接受一个函数作为入参,其实现延迟1s运行函数
// 其返回promise,结果为入参函数的返回结果
function delayCall(func) {
    return new Promise(resolve => {
        setTimeout(() => {
            const result = func();
            resolve(result);
        }, 1000);
    });
}
type IDelayCall= <T extends () => any>(func: T) => ReturnType<T>;
type IReturnType<T extends (...args: any) => any> = T extends(...args: any ) => inferR ? R : any
    
// 关键字[extends] 跟随泛型出现时,表示类型推断,其表达可类比三元表达式
// 如T === 判断类型?类型A:类型B
// 关键字[infer] 出现在类型推荐中,表示定义类型变量,可以用于指代类型
// 如该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中

工程应用

TypeScript工程应用——Web

image.png

TypeScript工程应用——Node

使用TSC编译,先通过手动运行,将工程中的ts文件编译为js,然后再运行js文件。

image.png

三、课后个人总结:

这节课学习了我之前没有接触过的typescript,从JavaScript和typescript的区别引入,介绍了typescript的很多基本语法,高级类型的应用等。在本地编译运行实践中领悟了typescript的运行过程,需要手动在命令行中输入tsc xx.ts,然后再同一文件夹下生成xx.js。

四、引用参考:

前端入门-理论篇

五、课后