TypeScript入门 | 青训营笔记

135 阅读7分钟

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

TypeScript入门

TS发展史

  • 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的一个特征是脚本语言,在浏览器执行的时候才会进行类型匹配
  • 静态类型
    • 提前去做类型的匹配
    • 如python,java都是需要先走编译流程
  • 弱类型语言
    • 特征:类型转换
    • 例如:当运行数字1和字符串1相加的时候,js是可以通过的
  • JavaScript
    • 是动态类型,弱类型语言
  • TypeScript
    • 是静态类型,弱类型语言
    • 可读性增强,基于语法解析TSDoc,ide能力得到增强
    • 可维护性增强:在编译阶段暴露大部分错误
    • 是JS的超集
      • 包含于兼容所有JS特性,可以共存
      • 支持渐进式引入与升级

基本语法

基础数据类型

  • 传统JavaScript(TypeScript可以直接用)
//字符串
const q = 'string';
//数字
const w =1;
//布尔值
const e = true;
//null
const r = null;
//undefined
const t = undefined;
  • 用TypeScript改进
//字符串
const q: string = 'string';
//数字
const w: number = 1;
//布尔值
const e: boolean =true;
//null
const r: null = null;
//undefined
const t: undefined = undefined;

对象类型

const bytedancer: IBytedancer ={
    jobId: 12345,
    name: 'L',
    sex: 'man',
    age: 28,
    hobby: 'swimming'
}

interface IBytedancer{
    //只读属性:约束属性补课在对象初始化外赋值
    readonly jobId: number;
    name: string;
    sex: 'man'|'woman'|'other';
    age: number;
    //可选属性:定义该属性可以不存在
    hobby?: string;
    //任意属性,约束所有对象属性都必须是该属性的子类型
    //any是ts中特有的类型,就是所有的类型都可以
    [key: string]: any;
}

函数类型

function add(x,y){
    return x+y;
}
const mult = (x,y)=> x*y;

上方是一个add函数和一个mult函数,那么如何给其添加类型声明呢

//直接为函数补上参数类型和返回值类型
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;

函数重载

//对getDate函数进行重载,timestamp为可缺省参数
function getDate(type: 'string',timestamp?: string): string;
function getDate(type: 'date',timestamp?: string): Date;
function getDate(type: 'string'|'date',timestamp?: string): Date | string{
    const date = new Date(timestamp);
    return type === 'string'?date.toLocaleString() :date;
}
const x = getDate('date');
const y = getDate('string', '2022-8-5');


//简化代码,通过interface实现以上函数重载
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"。
//类型匹配不上,为了让类型通过,需要让IGetDate的范围大于函数,只需将type:'string'改为any即可
const getDate2: IGetDate = (type, timestamp)=>{
    const date = new Date(timestamp);
    return type === 'string'?date.toLocaleString() : date;
}

数组类型

//类型+方括号表示
type IArr1 = number[];
//泛型表示
type IArr2 = Array<string | number | Record<string, number>>;
//元祖表示
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,{},[]];

TypeScript补充类型

//空类型,表示无赋值
type IEmptyFunction = ()=>void;
//任意类型,是所有类型的子类型
type IAnyType =any;
//枚举类型,支持枚举值到枚举名的正反向映射
enum EnumExample{
    add = "+",
    mult = "*"
}
EnumExample['add'] === '+';
EnumExample['+'] === 'add';

enum EColor{Mon,Tue,Wed,Thu,Fri,Sat,Sun};
EColor['Mon']===0;
EColor[0]==='Mon';
//泛型
type INumArr = Array<number>;

TypeScript泛型

  • 泛型基本语法
function getRepeatArr(target){
    return new Array(100).fill(target);
}
type IGetRepeatArr = (target: any) => any[];
//不预先指定具体类型,而在用的时候在指定类型
//泛型函数
type IGetRepeatArrR = <T>(target: T) =>T[];

//泛型接口,多泛型
interface IX<T,U>{
    key: T;
    val: U;
}
//泛型类
class IMan<T>{
    instance: T;
}
//泛型别名
type ITypeArr<T> = Array<T>;
  • 泛型高级用法
//泛型约束,限制泛型必须符合字符串
type IGetRepeatStringArr = <T extends string>(target: T)=>T[];
//报错,number类型的参数不能赋给string类型的参数
const getStrArr: IGetRepeatStringArr = target=>new Array(100).fill(target);

//泛型参数默认类型
type IGetRepeatArr<T = number>=(target: T) =>T[];
//没有传类型,默认number
const getRepeatArr: IGetRepeatArr = target =>new Array(100).fill(target)
//报错,string类型的参数不能赋给number类型的参数
getRepeatArr('123');

类型别名&类型断言

//通过type关键字定义了IObjArr的别名类型
type IObjArr = Arrat<{
    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>;
}

字符串/数字 字面量

  • 允许执行字符串/数字必须的固定值
//tag必须为html,body,div,span其中一个
type tag = 'html'|'body'|'div'|'span';
//num必须为1,3,5,7,9其中一个
type num = 1|3|5|7|9;

高级类型

联合/交叉类型

  • 为一些列表编写类型时,类型声明繁琐,存在较多重复
  • 原JS代码
const bookList = [{
    author: 'xx',
    type: 'history',
    range: '2012-2022',
},{
    author: 'll',
    type: 'story',
    theme: 'love',
}]
  • TS声明类型
interface IHistoryBook{
    author: string;
    type: string;
    range: string;
}
interface ISroryBook{
    author: string;
    type: string;
    theme: string;
}
type IBookList = Array<IHistoryBook | ISroryBook>;
  • 存在问题
    • 代码更多
    • 存在重复元素
    • type类型描述不准确,在booklist中其实只能取值2个,此处为string
  • 改进类型--联合/交叉类型
    • 联合类型:IA|IB,联合类型表示一个值可以是几种类型之一
    • 交叉类型:IA&IB,多种类型叠加到一起成为一种类型,包含了所需的所有类型的特性
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)
    }
}

//改进,类型守卫,定义一个函数,它的返回值是一个类型谓词,生效范围为子作用域
//类型谓词,返回值通过arg is告诉typescript,当返回值为true,函数类型一定是IA
function getIsIA(arg: IA|IB): arg is IA{
    //当存在a的时候断言他一定是IA类型
    return !!(arg as IA).a;
}
function log2(arg: IA|IB){
    if(getIsIA(arg)){
        console.log(arg.a1);
    }else{
        console.log(arg.b1);
    }
}


//实现函数logBook类型
function logBook(book: IBookItem){
    //联合类型+类型保护=自动类型推断
    if(book.type === 'history'){
        console.log(book.range)
    }else{
        console.log(book.theme);
    }
}

案例

  • 实现merge函数类型,要求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联动去除

改进:通过泛型实现

interface IMerge{
    <T extends Record<string,any>>(sourceObj: Partial<T>,targetObj: 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
//关键字【?】,通过设定对象可选选项,即可自动推导出子集类型

函数返回值类型

  • 实现函数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)=>infer R?R :any
/* T extends(..args:any),此处extends已经不是表示泛型限定,当【extends】关键字跟随泛型出现并且出现在类
型的定义中,表示泛型推断,其可表达类比三元表达式,如 T ===判断类型?类型A:类型B,在上方代码中其实是表示
T是否符合后面的类型,如果符合取R类型*/
//关键字【infer】出现在类型推荐中,表示定义类型变量,可以用于指代类型
//如改场景下,将函数的返回值作为变量,使用新泛型R表示,使用在类型推荐命中的结果中

工程应用

Web

  • webpack
    • 配置webpack loader(将webpack不能识别的转为可识别的)
    • 配置tsconfig.js文件
    • 运行webpack启动/打包
    • loader处理ts文件时会进行编译与类型检查

Node

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