这是我参与2022首次更文挑战的第14天,活动详情查看:2022首次更文挑战
授课老师:林皇
01. 背景
发展历史
- 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版本发布
什么是TypeScript?
JS:动态类型的弱类型语言 TS:静态类型的弱类型语言
弱类型语言的特征:类型转换。当我们运行一个字符串和数字相加的时候,会自动进行隐式类型转换。而在强类型语言中是没办法相加的。
静态类型
- 可读性增强: 基于语法解析TSDoC, ide增强
- 可维护性增强:在编译阶段暴露大部分错误 => 多人合作的大型项目中,获得更好的稳定性和开发效率
JS的超集
- 包含兼容所有JS的特性,支持共存
- 支持渐进式的引入与升级
02. 基础语法
这里主要通过对比JS来了解TypeScript。
JS的基础数据类型(还有个Symbol就不说了):
const str = 'string';
const num = 1;
const flag = true;
const obj = null;
const un = undefined;
TS的基本数据类型
/*字符串*/
const str: string = 'string';
/*数字*/
const num: number = 1;
/*布尔值*/
const flag: boolean = true ;
/*null*/
const obj: null = null ;
/* undefined */
const un: undefined = undefined;
对象类型
一般对象类型 Interface定义的名字,使用大写的I开头,比如IBytedancer。
除了设置可选的值,属性的类型,还可以在其中设置只读属性,可选属性,任意属性。
/*报错:无法分配到"jobId",因为它是只读属性*/
bytedancer.jobId = 12345;
/*成功:任意属性标注下可以添加任意属性*/
bytedancer.plateform = 'data'
/*报错:缺少属性"name", hobby可缺省 */
const bytedancer2:IBytedancer = {
jobId: 89757 ,
sex:'woman',
age: 18,
}
函数类型
js函数:
function add(x,y){
return x+y;
}
const mult = (x,y) => x*y;
ts函数:加强了对函数的入参和出参的类型限制
function add(x: number , y: number) : number {
return x + y;
}
const mult: (x: number, y: number) => number = (x, y) =>x * y;
也可以使用interface定义接口类型,再进行函数体赋值。(这么理解应该对吧。)
interface IMult{
(x:number,y:number):number;
}
const mult:IMult = (x,y)=> x*y;
函数重载
通过type来限制类型,?来表明是否缺省。第三个函数叫联合声明。可以单独声明每一个函数,也可以利用interface定义接口类型。
也可以利用interface定义接口类型:
数组类型
因为数组也是一种对象,所以可以使用interface进行定义,同时使用键值对。但是可以进行简化的操作。比如前三种定义。
泛型表示的尖括号表示
Typescript泛型
function getRepeatArr(target){
return new Array(100).fill(target);
}
type IGetReapeatArr = (target:any) => any[];
//不与先指定具体的类型,而在使用的时候再指定类型的一种特性
type IGetRepeatArrR = <T>(target:T)=> T[];
泛型不仅仅使用在函数中,作为变量指代可以用于泛型类,泛型别名
泛型的特性:泛型约束(extends)和泛型的默认类型
/*泛型约束:限制泛型必须符合字符串*/
type IGetRepeatStringArr = <T extends string>(target: T) => T[] ;
const getStrArr: IGetRepeatStringArr = target => new Array(100).fill(target);
/*报错:类型“number"的参数不能赋给类型"string"的参数*/
getStrArr(123);
/*泛型参数默认类型*/
type IGetRepeatArr<T = number> = (target: T) => T [];
const getRepeatArr:IGetRepeatArr = target => new Array(100).fill(:target) ;
/*报错:类型"string"的参数不能赋给类型“number"的参数*/
getRepeatArr('123');
类型别名type & 类型断言as
/*通过type关键字定义了I0bjArr的别名类型*/
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中的其一*/
typeIOddNumber= 1 | 3 | 5 | 7 | 9;
03. 高级类型
基础数据类型映射JS成TS的语法。虽然上面的类型看上去会多写一些代码,但是可以通过基础的数据类型定义高级类型,自动衍生出许多类型(类型联动)。
联合/交叉类型
例如存在两种书籍,故事书和历史书有相同的类型(作者,类型),也有不同的属性(主题,时期)。 如果为书籍列表编写类型,会导致类型声明繁琐,存在较多重复。
- 联合类型:
IA | IB- 联合类型表示一个值可以是几种类型之一
- 交叉类型:
IA & IB- 多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性
type IBookList = Array<{
author :string ;
} & ({
type :'history';
range: string ;
} | {
type: 'story' ;
theme: string;
})>
类型保护与类型守卫
如果通过属性来判断对象时,JS可以运行,但是TS会报错。访问联合类型时,只能访问联合类型中的交集部分。
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);
}
}
我们可以利用类型守卫进行改造:
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)) {//子作用域内全都是IA
console. log(arg.a1)
} else {
console. log(arg.b1);
}
}
使用内置的类型保护:typeof或者instanceof进行类型保护。
当两个类型完全没有交集的时候才使用类型守卫。
高级类型的定义(泛型子集)
第一种实现是比较安全的实现:
interface ISource0bj {
x?: string;
y?: string;
}
interface ITargetObj {
X: string ;
y: string
}
type IMerge = (source0bj: ISource0bj, target0bj:ITargetO0bj) => ITargetObj;
/**
*类型实现繁琐:若obj类型较为复杂,则声明source和target便需要大量重复2遍容易出错:若target增加/减少key,则需要source联动去除
*/
如果在使用的时候才明确子集对象,那么需要使用泛型的方式:
interface IMerge {
<T extends Record<string, any>>(sourceObj: Partial<T>, target0bj: T): T;
}
type IPartial<T extends Record<string, any>> = {
[P in keyof T]?: T[P] ;
}
//索引类型:关键字[keyof] ,其相当于取值对象中的所有key组成的字符串字面量,如
type IKeys = keyof { a:string; b: number };
// => type IKeys = "a" | "b"
//关键字[in] ,其相当于取值字符串字面量中的一种可能,配合泛型p,即表示每个key
//关键字[?] ,通过设定对象可选选项,即可自动推导出子集类型
由于Partial是一种非常常见的子集类型,所以不必自己声明,已经内置。下面的returnType也相同。
函数返回值类型ReturnType
type IDelayCall = <T extends () => any>(func: T) => ReturnType<T>;
type IReturnType<T extends (...args: any) => any> = T extends
(...args: any) => infer R ? R : any
关键字[extends] 跟随泛型出现时,表示类型推断,其表达可类比三元表达式
如T===判断类型?类型A:类型B (上述代码最后的extends)
关键字[infer] 出现在类型推荐中,表示定义类型变量,可以用于指代类型 如该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中。
04. 工程应用
浏览器Web | NodeJS
Web
- 配置webapack loader相关配置
- 配置tsconfig.js文件
- 运行webpack启动 / 打包
- loader处理ts文件时,会进行编译与类型检查
相关Loader:
- awesome-type-script-loader
- babel-loader
Nodejs
使用TSC编译过程:
- 安装Node与npm
- 配置tsconfig.js文件
- 使用npm安装tsc
- 使用tsc运行编译得 到js文件
05. Q&A
- 泛型约束和泛型默认参数可以一起使用。
- type是没有语义化的,inteerface是有语义的接口类型。当定义一个很基础内容的时候,可以使用interface。当想要做一个衍生类型的时候,可以使用类型别名。(type相对而言更加随意。)
- 如果使用类型守卫的时候,那么返回值必须是布尔值。(出参为arg is IA)