TypeScript | 青训营笔记

60 阅读5分钟

这是我参与【第四届青训营】笔记创作活动的第 9 天,今天学习了 TypeScript 的一些知识点

1. 什么是 TypeScript

1.1 发展历史

  • 2012-10:微软发布了 TypeScript 的第一个版本(0.8)
  • 2014-10:Angular 发布了基于 TypeScript 的 2.0 版本
  • 2015-04:微软发布了 Visual Studio Code(VSCode)
  • 2016-05:@types/react 发布,TypeScript 可开发 React
  • 2020-09:Vue 发布 3.0 版本,官方支持 TypeScript
  • 2021-11:v4.5 版本发布

1.2 对比 JS

JavaScript

  • 动态类型
  • 弱类型语言

TypeScript

  • 静态类型
  • 弱类型语言

解释 静态类型

  • 可读性增强:基于语法解析 TSDoc,IDE 增强
  • 可维护性增强:在编译阶段暴露大部分错误

在多人合作的大型项目中,获得更好的稳定性和开发效率

JS 的超集

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

1.3 编辑器推荐

  • Visual Studio Code (轻量级,插件系统丰富)
  • TypeScript Playground(在线)

image.png

2. 基本语法

2.1 基本数据类型

// 字符串
const q = 'string'
// 数字
const w = 1
// 布尔值
const e = true
// null
const r = null
// undefined
const t = undefined

==

// 字符串
const q: string = 'string'
// 数字
const w: number = 1
// 布尔值
const e: boolean = true
// null
const r: null = null
// undefined
const t:undefind = undefined

2.2 对象类型

const typedancer: IBytedancer = {
    jobId: 9303245,
    name: 'Lin',
    sex: 'man',
    age: 28,
    hobby: 'swimming'
}

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

image.png

2.3 函数类型

function add(x, y) {
    return x + y;
}
const mult = (x, y) => x + y;
interface IMult {
    (x: number, y: number): number
}
const mult: IMult = (x, y) => x + y;
function add(x: number, y: number): number {
    return x + y;
}
const mult = (x: number, y: number): number => x + y;

2.4 函数重载

image.png

image.png

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

2.6 补充类型

image.png

2.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>

image.png

2.8 类型词能够别名 & 类型断言

image.png

2.9 字符串 / 数字 字面量

image.png

3. 高级类型

3.1 联合 / 交叉类型

为书籍列表编写类型

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

类型声明繁琐,存在较多重复

interface IHistroyBook {
    author: string
    type: string
    range: striing
}

interface IStoryBook {
    author: string
    type: string
    theme: striing
}

type IBookList = Array<IHistoryBook | IStoryBook>

因此,我们可以使用 联合 / 交叉类型

联合类型:IA | IB; 联合类型表示一个值可以是集中类型之一

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

所以,我们可以把上面看的 interface 修改为如下


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

3.2 类型保护与类型守卫

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)) {
        console.log(arg.a1)
    } else {
        console.log(arg.b1)
    }
}

来看一道试题

实现函数 reverse

实现函数 logBook 类型

函数接收书本类型,并 logger 出相关特征


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

3.3 高级类型

实现 merge 函数类型

要求 sourceObj 必须为 targetObj 的子集


function merge1(sourceObj, targetObj) {
    const result = { ... sourceObj }
    for(let key in targeObj) {
        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 和 targeet 便需要大量重复 2 遍

容易出错:若 target 增加 / 减少 key,则需要 source 联动去除

所以我们可以实现 TS 的一些内置类型来帮助我们实现

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"

这一部分就是 TS 体操的内容,感兴趣的可以去github搜索 typescript-challenge学习,这里就不多做介绍了

3.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. 工程应用

4.1 Web--Webpack

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

相关 loader

  1. awesome-typescript-loader
  2. babel-loader

4.2 Node

4.2.1 使用 TSC 编译

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

image.png

总结

这篇文章主要介绍了一些 TypeScript 的一些基本语法,其中有很多的知识点,很多人觉得 ts 就是 js 加一层校验,其实不然,使用 ts 开发能大大降低 bug 的出现率以及合理的使用 ts 也能够带来不一样的开发体验~