了解TypeScript| 青训营笔记

60 阅读6分钟

为什么使用TypeScript

TypeScript对比JavaScript:

  • JavaScript
    • 动态类型,在执行时才做类型匹配
    • 弱类型语言
  • TypeScript
    • 静态类型,在执行前就进行类型匹配
    • 弱类型语言

TypeScript的优点:

  • 静态类型
    • 可读性增强:基于语法解析TSDoc,IDE增强
    • 可维护性增强:可以在编译时暴露大部分错误
    • 稳定性和开发效率更高
  • JS的超集
    • 包含与兼容所有JS特性,支持共存
    • 支持渐进式引入和升级

基本语法

基本数据类型

对于JS的数据类型到TS数据类型的对比:

JS代码const 变量名 = 变量值;

const s = 'string';
const n = 1;
const b = true;
const r = null;
const t = undefined;

TS代码consnt 变量名: 变量类型 = 变量值;

const s: string = 'string';
const n: number = 1;
const b: boolean = true;
const r: null = null;
const t: undefined = undefined;

对象数据类型

const bytedancer: IBytedancer = {
    jobId: 123456,
    name: 'Lil',
    sex: 'man',
    age: 18,
    hobby: 'play',
}

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

函数类型

为函数添加类型声明:

原始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;

函数重载

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');                    // x: Date
const y = getDate('string', '2018-11-24');    // y: string

使用接口简化代码:

interface IGetDate {
    // 因为匿名函数赋值会进行自动类型判断,判断返回值类型为Date | string,所以接受返回类型要更大
    (type: 'date', timestamp?: string): any;
    (type: 'string', timestamp?: string): any;
    (type: 'date' | 'string', timestamp?: string): Date | string;   
}

const getDate2: IGetDate = (type, timestamp) => {
    const date = new Date(timestamp);
    return type === 'string' ? date.toLocaleString() : date;
}

数组类型

数组是一种特殊的Object,可以有多种定义方式:

// type关键字用于给类型添加别名,方便调用
// 类型 + []表示
type Arr1 = number[];
// 泛型表示,定义可以包含的数据类型
type Arr2 = Array<string | number | Record<string, number>>
// 元组表示,定义每一项的数据类型
type Arr3 = [number, number, string, string];
// 接口表示,定义key的类型及值的类型
interface Arr4 {
    [key: number]: any;
}

const arr1: Arr1 = [1, 2, 3, 4, 5];
const arr2: Arr2 = [1, 2, '3', '4', { a: 1 }];
cosnt arr3: Arr3 = [1, 2, '3', '4'];
const arr4: Arr4 = ['string', () => null, {}, []];

TypeScript补充类型

// 空类型,表示无赋值
type IEmptyFunction = () => void;    // void一般作为函数的返回值类型,表示没有返回值
// 任意类型,是所有类型的并集
type IAnyType = any;
// 枚举类型,支持枚举值到枚举名的互相映射
enum EnumExample {
    add = '+';
    mult = '*';
}
EnumExample['add'] === '+';
EnumExample['*'] === 'mult';
// 泛型
type INumArr = Array<number>;

TypeScript泛型

泛型:是指不先指定具体类型,而在使用的时候在指定类型的一种特性。

泛型写在类/函数的名字之后,参数之前。

function getRepeatArr(target) {
    return new Array(100).fill(target);
}

type IGetRepeatArr = (target: any) => any[];
// 泛型
type IGetRepeatArr = <T>(target: T) => T[];

泛型使用场景:

// 泛型接口 多泛型
interface IX<T, U> {
    key: T;
    value: 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);

// 泛型参数默认类型
type IGetRepeatArr<T = number> =(target: T) => T[];    // 设置泛型参数默认类型为number
const getRepeatArr: IGetRepeatArr = target => new Array(100).fill(target);

类型别名 & 类型断言

使用type关键字定义类型别名:

type IObjArr = Arrray<{
    key: string;
    [objKey: string]: any;
}>

使用as关键字来进行类型断言:

function keyBy<T extends IObjArr>(objArr: Array<t>) {
    // 未指定类型时,result类型为{}
    const result = objArr.reduce((res, val, key) => {
        res[key] = val;
        return res;
    }, {});
    
    return result as Record<string, T>;
}

字符串/数字 字面量

使用type关键字指定字符串/数字允许的固定值:

type IDomTag = 'html' | 'body' | 'div' | 'span'; 
type IOddNumber = 1 | 3 | 5 | 7 | 9

高级类型

联合/交叉类型

联合类型:A | B,表示一个值可以是几种类型之一

交叉类型:A & B,表示多种类型叠加在一起成为一种类型,它包含了所需的所有类型的特性

type IBookList = Array<{
    author: string;    //公共属性
} & ({                  // 非公共属性与公共属性之间使用交叉类型连接
    type: 'history';
    range: string;
} | {                  // 非公共属性互相之间使用联合类型连接
    type: 'story';
    theme: string;
})>

const bookList = [{
    author: 'xiaoming',
    type: 'hitory',
    range: '2001-2021',
}, {
    autorh: 'xiaoli',
    type: 'story',
    theme: 'love',
}]

类型保护和类型守卫

类型保护:访问联合类型时,出于程序安全,仅能访问联合类型中的交集部分。 当需要访问联合类型中的各自属性时,可以使用类型守卫实现。

类型守卫:定义一个函数,其返回值是一个类型谓词,生效范围为子作用域

interface A { a: 1, a1: 2 }
interface B { b: 1, b1: 2 }

// 类型守卫
function getIsA(arg: A | B): arg is A {    // arg is A 类型谓词
    return !!(arg as A).a;                 // 返回值为true或false,表示是否为A类型
}

function log(arg: A | B) {
    if (getIsA(arg)) {
        console.log(arg.a1);
    } else {
        console.log(arg.b1)
    }
}

简化类型保护

使用typeofinstanceof

function reverse(target: string | Array<any>) {
    // typeof 类型保护
    if (typeof target === 'string') {
        return target.split('').reverse.join('');
    }
    // instanceof 类型保护
    if (target instanceof Object) {
        return target.reverse();
    }
}

使用联合类型 + 类型保护,可以进行自动类型判断,如上述的BookList例子:

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

类型子集表示

普通的子集表示,存在大量重复声明,且容易出错,Son和Father需要联动修改,且不能传入任意Object:

interface Son {
    x?: string;
    y?: string;
}

interface Father {
    x: string;
    y: string;
}

可以使用泛型优化子集声明:

interface IMerge {
    // Record<string, any>表示任意对象
    <T extends Record<string, any>>(son: Partial<T>, father: T): T;
}

type IPartial<T extends Record<string, any>> = {
    // keyof 用于获取对象中所有key组成的字面量
    // in 用于取值字符串字面量中的一种可能,配合泛型P即可匹配每个key
    [P in keyof T]?: T[P];    // T[P]表示T对象中的P属性的类型
}

函数返回值类型

对于延迟函数,传入一个函数,在一定时间后返回该函数的返回值。

函数的返回值类型由传入的函数决定,无法直接确定,所以需要通过泛型表达:

// <T extends () => any> 表示限定泛型类型为函数
type IDelayCall = <T extends () => any>(func: T) => ReturnType<T>;

// 关键字extends跟随泛型时表示类型推断,其写法类似三元表达式
// infer出现在类型推断中时,表示定义类型变量
// => infer R 表示将函数的返回类型定义为类型变量R
type IReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any

工程应用

Web

设置WebPack支持TypeScript步骤:

  1. 配置webapack loader相关配置,将webpack不能识别的文件转为可识别,常用的loader:
    • awesome typescript loader
    • babel loader
  2. 配置tsconfig.js文件,约束TypeScript的严格程度
  3. 运行webpack启动/打包
  4. loader处理ts文件时,会进行编译与类型检查,转化为js文件

NodeJS

设置NodeJS支持TypeScript,使用TSC编译,将ts文件转换为js文件:

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