TypeScript 入门 | 青训营笔记

76 阅读8分钟

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

一、本堂课重点内容:

TypeScript 的发展与基本语法
TypeScript 高级数据类型

二、详细知识点介绍:

TypeScript 的发展历史

image-20230119111149390

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. NullUndefined      默认情况下nullundefined是所有类型的子类型
10. Never                 never类型表示的是那些永不存在的值的类型。

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

JavaScript 与 TypeScript

image-20230119144842317.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-20230119204502432.png

TypeScript工程应用——Node

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

image-20230119204608935.png

三、课后个人总结:

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

四、引用参考:

前端入门-理论篇