【青训营】TypeScript入门笔记

169 阅读7分钟

这是我参与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

image.png

除了设置可选的值,属性的类型,还可以在其中设置只读属性,可选属性,任意属性。

/*报错:无法分配到"jobId",因为它是只读属性*/
bytedancer.jobId = 12345;
/*成功:任意属性标注下可以添加任意属性*/
bytedancer.plateform = 'data'
/*报错:缺少属性"name", hobby可缺省 */
const bytedancer2:IBytedancer = {
    jobId: 89757sex:'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定义接口类型。

image.png

也可以利用interface定义接口类型:

image.png

数组类型

image.png 因为数组也是一种对象,所以可以使用interface进行定义,同时使用键值对。但是可以进行简化的操作。比如前三种定义。

泛型表示的尖括号表示

Typescript泛型

function getRepeatArr(target){
    return new Array(100).fill(target);
}
type IGetReapeatArr = (target:any) => any[];
//不与先指定具体的类型,而在使用的时候再指定类型的一种特性
type IGetRepeatArrR = <T>(target:T)=> T[];

泛型不仅仅使用在函数中,作为变量指代可以用于泛型类,泛型别名

image.png

泛型的特性:泛型约束(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的语法。虽然上面的类型看上去会多写一些代码,但是可以通过基础的数据类型定义高级类型,自动衍生出许多类型(类型联动)。

联合/交叉类型

例如存在两种书籍,故事书和历史书有相同的类型(作者,类型),也有不同的属性(主题,时期)。 如果为书籍列表编写类型,会导致类型声明繁琐,存在较多重复。

image.png

  • 联合类型: 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进行类型保护。

image.png

当两个类型完全没有交集的时候才使用类型守卫。

高级类型的定义(泛型子集)

第一种实现是比较安全的实现: image.png

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

image.png

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

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

相关Loader:

  1. awesome-type-script-loader
  2. babel-loader

Nodejs

image.png 使用TSC编译过程:

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

05. Q&A

  • 泛型约束和泛型默认参数可以一起使用。
  • type是没有语义化的,inteerface是有语义的接口类型。当定义一个很基础内容的时候,可以使用interface。当想要做一个衍生类型的时候,可以使用类型别名。(type相对而言更加随意。)
  • 如果使用类型守卫的时候,那么返回值必须是布尔值。(出参为arg is IA)