Typescript | 青训营笔记

97 阅读7分钟

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

TypeScript 入门

为什么是 TypeScript?

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

TypeScript 和 JavaScript 的区别

TypeScript静态类型、弱类型语言;JavaScript动态类型、弱类型语言。

静态类型

  • 可读性增强:基于语法解析 TSDoc,ide 增强
  • 可维护性增强:在编译阶段暴露大部分错误
  • 在多人合作的大型项目中,获得更好的稳定性和开发效率

TS 是 JS 的超集:

  • 包含于兼容所有 JS 特性,支持共存
  • 支持渐进式引入与升级

TypeScript 开发环境搭建

  1. node.js 安装

  2. 使用 npm 全局安装 TypeScript

    npm i -g typescript
    
  3. 创建 ts 文件,并编写 ts 代码

  4. 使用 tsc 对 ts 文件进行编译

    在 ts 文件所在的目录下执行命令:

    tsc xxx.ts
    

    执行后即可生成相应的 js 文件

编辑器推荐:Visual Studio Code

基本语法

基础数据类型

JavaScript 的基本数据类型定义:

/* 字符串 */
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: 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",因为它是只读属性 */
bytedaner.jobId = 12345;
/* 成功 */
bytedaner.plateform = 'data';
/* 报错:缺少属性 "name",hobby 可缺省 */
const bytedaner2: IBytedancer = {
    jobId: 89757,
    sex: 'man',
    age: 28,
}

函数类型

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;

函数重载

/* 对 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"。ts(2322) */
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 ECorlor { Mon, Tue, Wed, Thu, Fri, Sat, Sun };
ECorlor['Mon'] === 0;
ECorlor[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[];
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 关键字定义了 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>;
}

字符串/数字 字面量

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

高级类型

联合/交叉类型

为书籍列表编写类型,会出现类型声明繁琐,存在较多重复的问题:

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<IHistoryBook | IStoryBook>;
  • 联合类型:IA | IB;联合类型表示一个值可以是几种类型之一

  • 交叉类型:IA & IB;多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性

    type IBookList = Array<{
        author: string;
    } & ({
        type: 'history';
        range: string;
    } | {
        type: 'story';
        theme: string;
    })>
    

类型保护与类型守卫

// 实现函数 reverse
// 其可将数组或字符串进行反转
function reverse(target: string | Array<any>) {
    /* typeof 保护 */
    if (typeof target === 'string') {
        return target.split('').reverse().join('');
    }
    /* instance 类型保护 */
    if (target instanceof Object) {
        return target.reverse();
    }
}
​
// 实现函数 logBook 类型
// 函数接受书本类型,并 logger 出相关特征
function logBook(book: IBookItem) {
    // 联合类型 + 类型保护 = 自动类型推断
    if (book.type === 'history') {
        console.log(book.range);
    } else {
        console.log(book.theme);
    }
}

高级类型

/**
* 实现 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 };
}
​
/**
 * 类型实现繁琐:若 obj 类型较为复杂,则声明 source 和 target 需要大量重复 2 遍
 * 容易出错:若 target 增加/减少 key,则需要 source 联动去除
**/
interface ISourceObj {
    x?: string;
    y?: string;
}
interface ITargetObj {
    x: string;
    y: string;
}
type IMerge = (sourceObj: ISourceObj, targetObj: ITargetObj) => ITargetObj;
​
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// 关键字 [extends] 跟随泛型出现时,表示类型推断,其表达可类比三元表达式
// 如 T === 判断类型 ? 类型 A : 类型 B// 关键字[infer]出现在类型推荐中,表示定义类型变量,可以用于指代类型
// 如该场景下,将函数的返回值类型作为变量,使用新泛型 R 表示,使用在类型推荐命中的结果中

工程应用

Web

Webpack

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

相关 loader:

  1. awsome-typescript-loader
  2. babel-loader

Node

使用 TSC 编译

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