TypeScript 各种使用示例

221 阅读7分钟

tsconfig.json 配置文件

{
    "compilerOptions": {
        "target": "es5",
        "lib": ["dom"],
        "strict": true
    },
    "exclude": ["node_modules"]
}

八个基本类型

  • boolean
let bool: boolean = false
  • number
let num: number = 1
  • string
let str: string = 'Hi'
// 字符串字面量类型
let str: 'name'
str = 'other name' // error
  • array
// 1. 推荐写法
let arr: number[] = [1, 2, 3]
let arr2: number | string[] = ['1', 2, '3']
// 2. 第二种写法(tslint可能会警告需要使用第一种写法,可以在tslint.json的rules中配置"array-rules": [false]关闭警告)
let arr: Array<number> = [1, 2, 3]
let arr2: Array<number | string> = [1, 2, '3']
  • object
let obj: object = { name: 'hello' }
  • symbol

  • null

let n: null = null
  • undefined
let u: undefined = undefined

注: 在没有配置 "strictNullChecks": true 规则时 nullundefined 可以赋值给任意类型的值

六个补充类型

  • 元组: 已知数组上每个位置上的数据类型
let tuple: [number, string, boolean]
tuple = [1, '2', true]
tuple[0] = 2
// TS2.6 版本之前, 含有越界元素(超出元组长度的元素)且越界元素是元组中指定的数据类型不会报错
// TS2.6 版本之后, 含有越界元素会报错
tuple = [1, '2', true, 3] // TS2.6前不会报错
tuple = [1, '2', true, null] // 都会报错, null不是元组指定类型中的任意一种
  • enum: 枚举
enum Status {
    SUCCESS = 200,
    NOT_FOUND = 404,
    REDIRECT = 301
}
// 默认是从 0 开始编号
enum Users {
    ZhangSan, // 0
    LiSi, // 1
    WangWu // 2
}
// 可以指定从多少开始编号
enum Users {
    ZhangSan = 5, // 5
    LiSi, // 6
    WangWu // 7
}
  • any: any类型的数据可以赋值为任意类型

  • void: 函数无返回值

const alertMessage = (msg: string): void => {
    window.alert(msg)
}
  • never: 永不存在值的类型
const throwError = (msg: string): never => {
    throw Error(msg)
}

const infiniteFunc = (): never => {
    while(true) {}
}

注:never 类型是任何类型的子类型,它可以赋值给任意类型;never 无子类型,除 never 类型外没有可以赋值给它的类型

  • unknown: unknown 相对于 any 是安全的,不像 any 类型的值是可以随便操作的

字面量类型

常量类型

  1. 字符串字面量类型
  2. 数字字面量类型
// 字符串字面量类型
let name: 'YM' = 'YM'
// name = 'LiSa' // 报错: 不能将类型“"LiSa"”分配给类型“"YM"”

// 数字字面量类型
let age: 80 = 80
// age = 46 // 报错: 不能将类型“46”分配给类型“80”

类型断言

有时候自己写代码的时候逻辑比较复杂,TS 并不能很好的对数据类型做判断,这时候可以使用 类型断言 自己做类型判断

两种定义方式:

  1. target as type (推荐)
  2. <type>target (JSX中不可用)
const getLength = (data: string | number): number => {
  if((<string>data).length != null) {
    return (target as string).length
  } else {
    return target.toString().length
  }
}

显式赋值断言

  1. 作用: 在 TS 做类型判断时表明变量一定会有值
  2. 操作符: !
interface VNode {
    tag?: string
    style?: {
        color?: string,
        [styles: string]: any
    }
}

const nodeDiv: VNode = {
    tag: 'div',
    style: {
        color: 'red'
    }
}

if(nodeDiv.style !== null) {
    // 当 tsconfig 开启 compilerOptions.strict 选项时, 没有声明显式赋值断言时会报出警告⚠️
    const { color } = nodeDiv.style! // 'red'
}

接口

TypeScript接口的基本使用

函数和函数参数

  • 类型定义
// 1
function add(x: number, y: number): number {
    return x + y
}
// 2
function add({x, y}: {x: number, y: number}): number {
    return x + y
}
// 3
let add: (x: number, y: number): number => number
add = (num1, num2) => num1 + num2 // num1、num2默认具有number类型
add = (num1: number, num2: number) => num1 + num2 // 或者直接指定类型
// 4
type Add = (x: number, y: number) => number
let add: Add = (num1, num2) => num1 + num2
// 5
interface Add {
    (x: number, y: number): number
}
let add: Add = (num1, num2) => num1 + num2
  • 可选参数
// 第三个参数可选
let add = (x: number, y: number, z?:number): number => {
    return x + y + (z || 0)
}
add(1, 2)
add(1, 2, 3)
  • 函数重载
function multiply(x: number, y: number): number;
function multiply(x: number): number;
function multiply(x: number, y?: any) {
    if(y == null) return x * 2
    return x * y
}
multiply(1, 2) // 2
multiply(2) // 4  

泛型

个人理解: 泛型相当于类型变量

  • 类型定义
// T 即为泛型, 泛型的名字可以随便定义
// 1
function copyValue<T>(value: T, length: number): T[] {
    return new Array(length).fill(value)
}
copyValue<number[]>([1, 2], 4).map(item => item.length) // [2, 2, 2, 2]
// 或不指定类型
copyValue([1, 2], 4).map(item => item.length) // [2, 2, 2, 2]

// 2
type CopyValue = <K>(value: K, length: number) => K[]
const copyValue: CopyValue = (value, length) => new Array(length).fill(value)
copyValue([1, 2], 4).map(item => item.length) // [2, 2, 2, 2]

// 3
interface CopyValue {
    <T>(value: T, length: number): T[]
}
const copyValue: CopyValue = (value, length) => new Array(length).fill(value)
copyValue([1, 2], 4).map(item => item.length) // [2, 2, 2, 2]

// 4. 将泛型定义提取至接口最顶部
interface CopyValue<T> {
    (value: T, length: number): T[]
}
const copyValue: CopyValue<number> = (value, length) => new Array(length).fill(value)
copyValue(1, 3).map(item => item + 1) // [2, 2, 2]
  • 多泛型
function copyValue<A, B>(value: [A, B], length: number): [A, B][] {
    return new Array(length).fill(value)
}
copyValue([1, '2'], 2).map(item => item[0] + item[1]) // ['12', '12']
  • 泛型约束
interface LengthAttr {
    readonly length: number
}
function getLength<T extends LengthAttr>(val: T): number {
    return val.length
}
getLength('123') // 3
getLength([1, 2, 3]) // 3
  • 泛型结合索引类型(keyof)
function getVal<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key]
}
getVal({name: 'YM', age: 41}, 'name')

  • 修饰符
    • public: 默认,可以在类创建的实例中访问,也可以通过 this 访问
    • private: 只能通过 this 访问, 且不能在子类中通过 this 访问
    • protected: 用来修饰 constructor 时,该类只能用来继承,不能直接实例化;当用来修饰属性或者方法时,只能在该类或者子类的 this 中访问
    • static: 只能通过类访问
    • readonly: 只读属性
// 父类
class Parent {
    // 没有添加修饰符时默认为 public
    age: number
    // 只能在该类的 this 中访问
    private id: string
    // 只能在类中访问
    static kind: string = 'people'
    // 只能在类或子类的 this 中访问
    protected score: number
    // 只能继承,不能实例化
    protected constructor(age: number, id: string, score: number) {
        this.age = age
        this.id = id
        this.score = score
    }

    getAge() {
        return this.age
    }

    getId() {
        return this.id
    }

    getScore() {
        return this.score
    }

    getKind() {
        return Parent.kind
    }
}

// 子类
class Child extends Parent {
    constructor(age: number, id: string, score: number) {
        super(age, id, score)
    }

    setAge(age: number) {
        this.age = age
    }

    setScore(score: number) {
        this.score = score
    }

    setId(id: string) {
        // this.id = id // 报错: 属性“id”为私有属性,只能在类“Parent”中访问
    }
}

// static 属性,只能通过类访问
Parent.kind = 'other'
Child.kind || Parent.kind // other

const c = new Child(40, '168888', 1)
// public 属性
c.age || c.getAge() // 40
c.age = 88 // 或 c.setAge(88)
c.age || c.getAge() // 88

// protected 属性
c.getScore() // 1
c.setScore(2)
// c.score // 报错: 属性“score”受保护,只能在类“Parent”及其子类中访问。
c.getScore() // 2

// private 属性
// c.id // 报错: 属性“id”为私有属性,只能在类“Parent”中访问。
c.getId() // 168888
  • 抽象类: 抽象类只能通过继承作为父类使用,抽象类中可以定义抽象属性和抽象方法,抽象属性和抽象方法中只包含类型定义,没有具体的实现逻辑
abstract class Parent {
    abstract name: string
    abstract getName(): string
    
    constructor() {}
}

class Child extends Parent {
    name: string
    constructor(name: string) {
        super()
        this.name = name
    }
    
    getName() {
        return this.name
    }
}

const c = new Child('YM')
c.getName() // YM
  • 存取器(get/set)
class Parent {
    private _data: object
    
    constructor({ data }: { data: () => object }) {
        this.data = data()
    }
    
    get data() {
        return this._data
    }
    set data(data: object) {
        return this._data = data
    }
}

const p = new Parent({
    data() {
        return {
            name: 'YM',
            age: 41
        }
    }
})

类实现接口

  1. 操作符: implements
interface Common<State> {
    state: State

    componentDidMount?: () => void

    componentWillUnmount?: () => void
}

interface State {
    id?: number
    currentPage: number
    pageSize: number
}

class App implements Common<State> {
    state = {
        currentPage: 1,
        pageSize: 50
    }

    componentDidMount() {
        console.log('component did mount')
    }
}

索引类型

  1. 索引类型查询操作符: keyof
  2. 索引类型访问操作符: []
interface Params {
    id: number
    name: string
    [props: string]: any
}

function getValues<T extends Params, K extends keyof T>(params: T, keys: K[]): T[K][] {
    return keys.map(key => params[key])
}

getValues({
    id: 111,
    name: 'YM',
    page: 1
}, ['name', 'page']) // ['YM', 1]

Record类型

Record类型是 TS 内置的映射类型,可以比较方便地实现相同数据类型的定义,拿来定义对象的数据类型

interface Fields {
    name: string
    id: number
}

type Names = 'YM' | 'LiSa' | 'Siri'

// Record<'YM' | 'LiSa' | 'Siri', { name: string, id: number }>
const obj: Record<Names, Fields> = {
    YM: {
        name: 'YM',
        id: 1
    },
    LiSa: {
        name: 'LiSa',
        id: 2
    },
    Siri: {
        name: 'Siri',
        id: 3
    }
}

// 或者将 Fields 字段值改成统一类型
const obj2: Record<typeof Fields, string> = {
    name: 'YM',
    id: '1'
}

// 如果不使用 Record 内置类型自己实现
interface Custom {
    YM: Fields
    LiSa: Fields
    Siri: Fields
}
// 可以看的出来,在字段少的时候还好一些,多一些的话有几个字段就需要写几次

映射类型

通过映射类型可以基于其他类型创建新类型

// 1. 给字段添加可选属性
interface VNode {
    tag: string
    id: string
    style: Record<'background' | 'color', string>
}

type PartialType<T> = {
    [P in keyof T]?: T[P]
}
type PartialVNode = PartialType<VNode>

const nodeDiv: PartialVNode = {
    tag: 'div'
}

// 2. 给字段添加只读属性
type ReadonlyType<T> = {
    readonly [P in keyof T]: T[P]
}
type ReadonlyVNode = ReadonlyType<VNode>

const nodeH1: ReadonlyVNode = {
    tag: 'h1',
    id: 'nodeId',
    style: {
        color: 'blue',
        background: 'red'
    }
}

注:TS 已经内置了可选映射类型 Partial 和只读映射类型 Readonly, 还有两种内置的映射类型是 PickRecord

/**
 * Pick 实现方式:
 * type Pick<T, K extends keyof T> = {
 *     [P in K]: T[P]
 * }
 */
interface Info {
    name: string
    age: number
    id: string
}
type PickInfo = 'name' | 'age'
const info: Pick<Info, PickInfo> = {
    name: 'YM',
    age: 88
}
/**
 * Record 实现方式:
 * type Record<K extends string | number, U> = {
 *     [P in K]: U
 * }
 */

可以通过 +- 符号来增加或移除特定修饰符

// 删除只读属性,添加可选属性
type RemoveReadonlyAddPartial<T> = {
    -readonly [P in keyof T]+?: T[P] 
}

interface Info {
    name: string
    age: number
}

const info: RemoveReadonlyAddPartial<Readonly<Info>> = {
    age: 78
}

info.age = 79