TypeScript | 青训营笔记

106 阅读6分钟

这是我参与第五届青训营伴学笔记创作活动的第三天!

学习详情

1、TypeScript?

发展:

  • 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版本发布

对比:

  • JS:动态类型(在执行阶段才确定的类型匹配),弱类型语言,脚本语言
  • TS:静态类型(java,Python),弱类型语言(类型转换)

优势:

  • 静态类型:
    • 可读性增强:基于语法解析TSDoc,ide增强
    • 可维护性增强:在编译阶段暴露大部分错误
    • 多人合作的大型项目中,获得更好的稳定性和开发效率
  • JS的超集:
    • 包含于兼容所有JS特性,支持共存
    • 支持渐进式引入与升级

2、基本语法

(1)基础数据类型:

字符串

//js:
const q = 'string';
//ts:
const q: string = 'string';

数字

//js:
const w = 1;
//ts:
const w: number = 1;

布尔值

//js:
const e = true;
//ts:
const e: boolean = true;

null

//js:
const r: null;
//ts:
const r: null = null;

undefined

//js:
const t = undefined;
//ts:
const t: undefined = undefined;

(2)对象类型

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

const bytedancer: IBytedancer = {
    jobId: 90009,
    name: 'chen',
    sex: 'woman',
    age: 20,
    hobby: 'watch',
}

/*报错:无法分配到“jobId”,因为它是只读属性*/
bytedancer.jobId = 123456;

/*成功:任意属性标注下可以添加任意属性*/
bytedancer.plateform = 'data';

/*报错:缺少属性"name",hobby可缺省*/
const bytedancer2: IBytedancer = {
    jobId: 88888,
    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

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<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, {}, []];

(6)TypeScript补充类型

/*空类型,表示无赋值*/
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<number>;

(7)泛型

/*函数中*/
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[];
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');

(8)类型别名&类型断言

/*通过type关键字定义了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>;
}

(9)字符串/数字 字面量

/*允许指定字符串/数字必须的固定值*/
/*IDomTag必须为html,body,div,span中的其一*/
type IDomTag = 'html' | 'body' | 'div' | 'span';
/*IOddNumber必须为1、3、5、7、9中的其一*/
type IOddNumber = 1 | 3 | 5 | 5 | 7 | 9;

3、高级类型

(1)联合/交叉类型

  • 联合类型: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: 'stroy';
    theme: string;
})>

(2)类型保护与类型守卫

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 log(arg: IA | IB) {
    if(getIsIA(arg)) {
        console.log(arg.a1)
    } else {
        console.log(arg.b1);
    }
}
//实现函数logBook类型
//函数接受书本类型,并logger出相关特征
function logBook(book: IBookItem){
    //联合类型+类型保护=自动类型判断
    if(book.type === 'histroy') {
        console.log(book.range)
    } else {
        console.log(book.theme);
    }
}

(3)高级类型

  • 实现merge函数类型
  • 要求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联动去除
*/
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
//关键字【?】,通过设定对象可选选项,即可自动推导出子集类型

(4)函数返回值类型

  • 实现函数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

//关键字【extends】跟随泛型出现时,表示类型推断,其表达可类比三元表达式
//如 T === 判断类型?类型A:类型B

//关键字【infer】出现在类型推断中,表示定义类型变量,可以用于指代类型
//如 该场景下,将函数的返回值类型作为变量,使用新泛型R表示,使用在类型推荐命中的结果中

4、工程应用

浏览器web NodeJs

  • webpack
    • 配置webpack loader相关配置
    • 配置tsconfig.js文件
    • 运行webpack启动/打包
    • loader处理ts文件时,会进行编译与类型检查
    • 相关loader:
      • awesome-typescript-loader
      • babel-loader
  • NodeJs 使用TSC编译
    • 安装Node与npm
    • 配置tsconfig.js文件
    • 使用npm安装tsc
    • 使用tsc运行编译得到js文件