目录
一、为什么什么是TypeScript?
1、发展历史
2、typescript与javascript
3、静态类型的好处
二、基础语法
1、基础数据类型
2、补充类型
3、泛型
4、泛型的高级语法
5、类型别名&类型断言
6、字符串/数字 字面量
三、高级类型
1、联合/交叉类型
2、类型保护与类型守卫
3、merge函数
4、函数返回值类型
四、工程应用
1、浏览器Web--webpack
2、NodeJs
五、学习总结
实例:当函数及传入的参数可能为任意类型时
2012-10:微软发布了TypeScript第一个版本(0.8)
2014-10: Angular 发布了基于TypeScript的2.0版本
2015-04:微软发布了 Visual Studio Code
2016-05: @types/react 发布,TypeScript可开发React
2020-09:Vue发布了3.0版本,官方支持 TypeScript
2021-11:v4.5版本发布
(1)typescript:静态,弱类型的语言
(2)javascript:动态,弱类型的语言
(3)动态、静态类型
①动态类型:是在执行阶段才确定类型的匹配
javascript是脚本语言,代码可以直接贴到浏览器的控制台执行,执行过程中,浏览器js解析引擎才会进行类型的匹配,当输入类型错误时,报错即动态类型的一个特征,是在运行是才进行类型的检验/匹配
②静态类型:会提前进行类型的检验/匹配
(4)强、弱类型
①弱类型,当字符串'1'和数字1相加时,js在执行时会进行隐式类型转换
②强类型,字符串'1'和数字1不能相加
(1)可读性增强:基于ts的语法解析TSDoc,ide可以得到增强
当写完一段代码(函数/对象),过一段时间就容易遗忘,难以整理成文档时
①TSDoc语法解析,可以自动生成文档,同时类型也会被说明含义,也就是在写类型时,相当于在写文档,节约解读代码所花费的时间
②ide:有很强的类型提示
(2)可维护性增强:在编译阶段暴露大部分错误
编译暴露的问题:语法拼写错误,类型匹配错误(当把字符串传给函数,但是函数需要的是对象)等等 基于上面两点,使用typescript在多人合作的大型项目中,能获得更好的稳定性和开发效率
(3)ts是js的超集
①包含于兼容所有js特性,支持共存
②支持渐进式引入与升级
(1)常见的基础数据
/*ts*/
/*字符串*/
const q: string = 'string' ;
/*数字*/
const w: number = 1;
/*布尔值*/
const e: boolean = true;
/* null */
const r: null = null;
/* undefined */
const t: undefined = undefined;
/*js*/
/*字符串*/
var q = 'string';
/*数字*/
var w = 1;
/*布尔值*/
var e = true;
/* null */
var r = null;
/* undefined */
var t = undefined;
(2)对象类型
自定义对象类型
const bytedancer: IBytedancer = {
jobId: 9303245,
name: 'Lin',
sex: 'man',
age: 28,
hobby: 'swimming',
}
interface IBytedancer {
/*只读属性:约束属性不可在对象初始化外赋值*/
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,
}
(3)函数类型
function add(x,y){
return x + y;
}
const mult = (x,y) => x*y;
①直接在函数上进行类型的补充
function add(x: number, y: number): number {
return x + y;
}
const mult: (x: number, y: number) => number = (x, y) => x * y;
②给函数变量赋值一个函数类型声明
interface IMult{
(x:number,y:number):number;
}
const mult:IMult=(x,y)=>x*y;
(4)函数重载
/*对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"*/
const getDate2: IGetDate = (type, timestamp) => {
const date = new Date(timestamp);
return type === 'string' ? date.toLocaleString() : date;
}
(5)数组类型
/*「类型+方括号」表示*/
type IArr1 = number[];
/*泛型表示*/
type IArr2 = Array>;
/*元祖表示*/
type IArr3 = [number, number, string, string];
/*接口表示*/
interface IArr4 {
[key: number]: any;
}
const arr1: 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, {}, []];
/*空类型,表示无赋值*/
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 INumArr = Array;
(1)场景1
①设置一个数组,可以传入任意类型,希望传入数字时输出数字的Array,传入字符串时输出字符串的Array,不然会组成数据丢失的情况,因为会被重写成any,达不到效果
②那么就需要泛型,可以不预先指定数据的类型,在数据使用的时候,根据内容才确定其类型
function getRepeatArr(target) {
return new Array(100).fill(target);
}
type IGetRepeatArr = (target: any) => any[];
/*不预先指定具体的类型,而在使用的时候再指定类型的一种特性*/
type IGetRepeatArrR = (target: T) => T[];
(2)其它场景
泛型其实就是一种变量指代
/*泛型接口&多泛型*/
interface IX {
key: T;
val: U;
}
/*泛型类*/
class IMan{
instance: T;
}
/*泛型别名*/
type ITypeArr = Array;
(3)注意定义区别
①函数:在函数定义的()前,使用<>,<>中的内容就是泛型
②类/别名/接口:都是在该类型别名后,使用<>,<>中的内容就是泛型
(1)泛型约束
泛型需要限制在一定范围内(如:它可以是传入的时候被使用但是必须有一个限定)
(2)泛型参数默认类型
/*泛型约束:限制泛型必须符合字符串*/
// extends表示类型约束
type IGetRepeatStringArr = (target: T) => T[];
const getStrArr: IGetRepeatStringArr = target => new Array(100).fill(target);
/*报错:类型"“number”的参数不能赋给类型“string"的参数*/
getStrArr(123);
/*泛型参数默认类型*/
type IGetRepeatArr = (target: T)=> T[];
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);
/*报错:类型“string”的参数不能赋给类型"“number"的参数*/
getRepeatArr( '123');
两种区别:上面是函数泛型,下面是类型别名的泛型
/*通过type关键字定义了I0bjArr的别名类型*/
// 用一个变量承载比较复杂的类型
type IObjArr = Array<{
key: string;
[objKey: string]: any;
}>
// keyBy函数将arr转换成object
// reduce()函数给result传入一个空obj(因为result的类型没有被指定,ts会进行默认类型推断)
function keyBy(objArr: Array) {
/*未指定类型时, result类型为f}*/
const result = objArr.reduce((res, val, key) => {
res[key] = val;
return res;
}, {});
/* 通过as关键字,断言result类型为正确类型*/
return result as Record;
}
/*允许指定字符串/数字必须的固定值*/
/* IDomTag必须为html、body、div、span中的其一*/
type IDomTag = 'html' | 'body' | 'div' | 'span' ;
/* IOddNumber必须为1、3、5、7、9中的其一*/
type IOddNumber = 1 | 3 | 5 | 7 | 9;
(1)原始写法
// 为书籍列表编写类型
const bookList = [{
author: 'xiaoming',
type: 'history',
range: '2001-2021',
}, {
author: 'xiaoli',
type: 'story',
theme: 'love',
}]
// 类型声明繁琐,存在较多重复
interface IHistoryBook {
author: string;
type: string;
range: string
}
interface IStoryBook {
author: string;
type: string;
theme: string
}
type IBookList = Array;
(2)联合交叉类型法
①联合类型:IA|IB;联合类型表示一个值可以是几种类型之一
②交叉类型:IA &IB;多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type IBookList = Array<{
author: string;
} & ({
type: 'history';
range: string;
} | {
type: 'story';
theme: string;
})>
(1)原始写法
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);
}
}
(2)类型守卫
①额外定义了getIsIA()函数,可以接收一个参数【IA或IB】
②判断时写新语法,类型谓词is,判断是否是IA
③返回时使用断言as,当存在a时,断言一定是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);
}
}
(3)typeof、instance类型保护
reverse()只有数组才支持,字符串不支持,要先把字符串转为数组
//实现函数reverse
//其可将数组或字符串进行反转
function reverse(target: string | Array) {
/* typeof类型保护*/
if (typeof target === 'string') {
return target.split("").reverse().join("");
}
/* instance类型保护*/
if (target instanceof Object) {
return target.reverse();
}
}
(4)总结
①当两个类型完全没有任何交集时,才需要写类型守卫
②回到前面书籍数据的例子
//实现函数logBook类型
//函数接受书本类型,并logger出相关特征
function logBook(book: IBookItem) {
//联合类型+类型保护=自动类型推断
if (book.type === 'history') {
console.log(book.range)
} else {
console.log(book.theme);
}
}
(1)传统类型定义
/**
*merge函数类型,实现合并sourceObj、targetObj
*要求sourceObj必须为targetObj的子集
*/
// 方法一
function merge1(sourceObj, targetObj) {
const result = { ...sourceObj };
for (let key in targetObj) {
const itemVal = sourceObj[key];
itemVal && (result[key] = itemVal);
}
return result;
}
// 方法二
function merge2(sourceObj, targetObj) {
return { ...sourceObj, ...targetObj };
}
interface ISourceObj {
x?: string;
y?: string;
}
interface ITargetObj {
x: string;
y: string
}
type IMerge = (sourceObj: ISourceObj, targetObj: ITargetObj) => ITargetObj;
/**
*类型实现繁琐:若obj类型较为复杂,则声明source和target便需要大量重复2遍
*容易出错:若target增加/减少key,则需要source联动去除
*/
(2)使用泛型
①IMerge对象
注意:函数的泛型定义是在()前面定义的,类型泛型定义是在类型后面定义的
②IPartial
interface IMerge {
>(sourceObj: Partial, targetObj: T): T;
}
type IPartial> = {
[P in keyof T]?: T[P];
}
(1)对函数类型的定义,就是对函数的入参出参定义
下面的delayCall函数例子中,入参是一个函数,而出参是一个Promise函数返回值
//实现函数delayCall的类型声明
//delayCall接受一个函数作为入参,其实现延迟1s运行函数
//其返回promise,结果为入参函数的返回结果
function delayCall(func) {
return new Promise(resolve => {
setTimeout(() => {
const result = func();
resolve(result);
}, 1000);
});
}
type IDelayCall = any>(func: T) => ReturnType;
type IReturnType any> = T extends (...args: any) => infer R ? R : any
//关键字【extends】跟随泛型定义出现时,表示类型推断,其表达可类比三元表达式
//如T===判断类型?类型A:类型B
//关键字【infer】出现在类型推荐中,表示定义类型变量,可以用于指代类型
//如该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中
(1)配置webapack loader相关配置
webapack loader:将webpack中不能识别的文件,转换成webpack可以识别的文件
(2)配置tsconfig.js文件
(3)运行webpack启动/打包
(4)loader处理ts文件时,会进行编译与类型检查
(1)使用TSC进行编译:将ts文件转换成js文件
(2)过程
①安装Node与npm
②配置tsconfig.js立件
③使用npm安装tsc
④使用tsc运行编译得到js文件
通过本次学习,对ts的泛型有了更清晰明确的认识
(1)普通写法
给函数及传入的参数定义any类型
function identity(msg:any):any{
return msg;
}
由于算法的复用,会导致这个函数可以接收任何类型的msg参数,这样就丢失了一些信息:传入的类型与返回的类型应该是相同的
(2)泛型写法
给函数添加泛型类型变量T,T能够捕获用户传入的类型,然后使用这个类型
实现在函数执行时才去获取类型
function identity(msg:T):T{
return msg;
}
“精辟总结”:普通函数要求我们对值编程,泛型则要求我们对类型编程