typescript

环境搭建

全局安装

1
npm i -g typescript

基础类型

  1. JS已有类型
    原始类型: number/string/boolean/null/undefined/symbol。
    对象类型:object (包括,数组、对象、函数等对象)。

  2. TS 新增类型
    联合类型、自定义类型(类型别名)、接口、元组、字面量类型、枚举、void、any等。


  • 原始类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 类型都是小写
    const a: string = 's'
    const b: number = 12
    const c: null = null
    const d: undefined = undefined
    const e: symbol = Symbol()
    const f: boolean = true
    // 联合类型
    const g: boolean | string = ''
  • 数组
    1
    2
    3
    // 数组大写,基础类型小写,推荐number[]这种形式
    const arr2: number[] = [1]
    const arr1: Array<string> = ['d']
  • 联合类型
    1
    2
    3
    // 单数杠分割  |  表示联合类型
    const arr3: (number | string)[] = [1,'s']
    const arr4: Array<string|number|boolean> = [1,'f', false]
  • 类型别名: type关键字
    1
    2
    3
    4
    5
    // 简化类型书写 type关键字 自定义类型
    type customArr = (number|string)[]
    const as_arr: customArr = [1, '']

    // 与interface的区别:1. type用等号,2. 接口只能定义对象,别名能定义全部
  • 函数
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    // 函数类型指给参数和返回值设置类型
    // 1. 单独指定参数和返回值类型
    function add (num1: number, num2: number): number{ // 参数类型直接在形参定义,返回值类型在参数后定义
    return num1 + num2
    }
    const add = (num1: number, num2: number): number => { // 函数表达式形式也一样
    return num1 + num2
    }
    // 2. 同时指定参数和返回值类型
    // 只用于函数表达式形式,相当于给变量定义类型
    // const add = () => {} 演变过程1: 正常定义函数
    // const add: (num1: number, num2: number) => number 演变过程2: 给变量定义函数类型
    const add: (num1: number, num2:number) => number = (num1, num2) => (num1 + num2) // 演变过程3:结合前两步

    // void:定义没有返回值的函数
    function clg (s: string): void {
    clg(s)
    }

    // 可选参数:?定义,需保证可选参数在最后边,其后边不能有必选参数
    function fn_or (s1:string, s2?:string): void {
    clg(s1, s2)
    }

    // 参数默认值
    function fn_def (num1: number = 10, num2?: number): void {}
  • 对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    // 单行内定义类型用分号分隔(但是用逗号也没问题。。。)
    const obj: {name: string; sayHi: ()=>void; greet(s:string):void} = {
    name: 'gss',
    sayHi() {},
    greet(s) {clg(s)}
    }
    // 可以用换号替换分号(不省略或用逗号也没问题。。。)
    const obj: {
    name: string
    sayHi: ()=>void // 函数的两种形式
    greet(s:string):void
    } = {
    name: 'gss',
    sayHi() {},
    greet(s) {clg(s)}
    }
    // 可选属性 ?
    function myAxios(config: {url:string, methods?: string}) {}
  • 接口
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    // interface关键字,语法类似class,只用于定义对象类型
    interface Person {
    name: string
    sayHi: () => void
    }

    const p1: Person = { name: 'vic', sayHi() {} }

    // 接口继承extends,实现复用
    interface Person2 extends Person {
    greet(s: string): void
    }

    const p2: Person2 = {
    name: 'vic',
    sayHi() {},
    greet(s) {}
    }

    // 与type别名的区别
    // 1. 别名用等号
    type Person3 = {
    name: string
    sayHi: () => void
    }
    // 2. 别名不仅能用于对象定义
    type PersonName = string | number | null
    // 3. 别名没有继承
  • 元组
    1
    2
    3
    // 特殊的数组类型:确定的长度和索引对应元素类型是确认的
    const arr: number[] = [] // 普通数组,元素个数不定
    const o_arr: [number, string] = [1, 's'] // 元组:两个元素,类型分别是number,string
  • 类型断言
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    // 断言就是将一个类型改为另一个类型
    // 但是两个类型间是有关联的,需要有包含关系才行
    // 四种情况:
    // 1. 联合类型断言为其中的具体类型
    // 2. 父类断言成子类
    // 3. 任何类型断言为any
    // 4. any断言为任何具体类型

    interface Animal {
    name: string
    }

    interface Cat extends Animal {
    run(): void
    }

    interface Fish extends Animal {
    swim(): any
    }

    function fn_as (an: Cat | Fish) { // 1. 联合类型断言为其中的具体类型
    // an.run() 直接调用报错
    // an.swim()
    (an as Cat).run(); // 断言为具体类型在调用相应方法
    (an as Fish).swim()
    }

    function fn_as2(an:Animal) { // 2. 父类断言成子类
    (an as Cat).run() // 父类断言为子类
    }

    // 3. 任何类型断言为any
    // window.foo = 1 // window上没有定义foo,报错
    (window as any).foo = 1 // 断言为any就不报错了

    // 4. any断言为任何具体类型
    let fish: Fish = {
    name: '石斑',
    swim() { // swim 返回类型是any
    return 'swim'
    }
    }
    let swim = fish.swim() as string // 将any类型断言为string

    // 注意:在dom操作时可能会用到
    // <a href='www.baidu.com' id='a'></a>
    let dom = document.getElementById('a') // 此时获取的类型是HTMLElement类型。
    //但这个类型下是无法获取a标签的href属性的,因为这个类型属于父类,针对不同标签还有不同类型
    // document.getElementsByTagName('a') 通过这种方式可以获取各个标签的具体类型,a标签是HTMLAnchorElement
    let dom2 = document.getElementById('a') as HTMLAnchorElement // 通过断言,使用具体类型,就可以使用相应的属性了
  • 字面量类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    // 字面量类型就是类型和值是固定相等的,类似于变量声明
    let age: 18 = 18; // age的类型是18.它的值也只能是18
    age = 19 // 会报类型错误

    // const 定义变量,不规定类型的话,不会自动推论类型,而是形成字面量类型
    const zml = '字面量' // zml变量不会自动推论为string(let会),而是zml的类型就是'字面量'

    // 通常配合联合类型使用
    function point(position: 'up' | 'down' | 'left' | 'right') {}
    point('up') // 参数只能是其中一个
  • enum 枚举类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    // enum 关键字,枚举类型和其他类型不同,他是有值的,会被编译成js。

    // 默认值是自增数字,0开始
    enum Position {
    Up, // 0
    Down, // 1
    Left, // 2
    Right // 3
    }

    function point2 (position: Position) {
    console.log(position) // 0,1,2,3
    }
    point2(Position.Down)

    // 字符串enum
    enum PositionStr { // 如果定义了字符串,必须全部设置,因为此时没法自增
    Up = 'up',
    Down = 'down',
    Left = 'left',
    Right = 'right'
    }

    console.log(PositionStr.Up)

    // enum 类型编译为js如下:是自执行函数
    // var PositionStr;
    // (function (PositionStr) {
    // PositionStr["Up"] = "up";
    // PositionStr["Down"] = "down";
    // PositionStr["Left"] = "left";
    // PositionStr["Right"] = "right";
    // })(PositionStr || (PositionStr = {}));

    高级类型

  • class
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    // ts中class和js中职能一样,写法有些区别,另外ts中class也是类型
    class Person {
    userName: string // ts中实例属性需要单独定义
    age = 18
    constructor(name: string) { // 构造函数需指定参数类型,不能指定返回类型
    this.userName = name
    // this.sex // 未定义的属性访问不到,报错
    }
    sayHi(word: string) {} // 实例方法
    }

    let p = new Person('ss') // p的类型是Person

    // 类的继承 extends
    class Tom extends Person {
    sex: number
    constructor(name: string, sex: number, age: number) {
    super(name)
    super.age = age
    this.sex = sex
    }
    }

    // 类 对 接口的实现implements
    // 实现接口中定义的属性、方法
    interface FormatPerson {
    greet(): void
    color: string
    }
    class Imp_person implements FormatPerson{
    color = 'red'
    greet() {}
    hob = ''
    }

    // 类修饰符:public、protected、private、readonly
    // 公共(默认) | 受保护 | 私有 | 只读
    // 类、子类实例都能访问 | 类、子类中能访问,实例不能 | 自己类中能访问,子类、实例不能 | 作用与属性,只能赋初始值,不能修改
    class Person {
    userName = 'gss' // 公共 public
    protected age = 18 // 受保护的,此类和子类访问
    private sex = 1 // 私有的,此类访问
    readonly hob: string // 只读,只能初始化赋值一次后续不能更改
    readonly hob2 = '爱好' // readonly类似const,形成字面量类型,下边构造函数中相当于没改变值
    constructor(hob: string, hob2: '爱好') {
    this.hob = hob
    this.hob2 = hob2 // 字面量类型,这样写没意义。
    }
    sayHi(){
    clg(this.userName, this.age, this.sex) // 都能访问到
    this.changeHob()
    }
    private changeHob() {
    // this.hob = '' // 报错,只读不能修改
    }
    }
  • 交叉类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    // 交叉类型就是整合多个类型为一个类型, 多用于接口; 用&连接

    interface jx {
    name: string
    }

    interface jx2 {
    age: number
    }

    type jx3 = jx & jx2 // 合并两个接口
    const jx_v: jx3 = {
    name: '',
    age: 1
    }

    // 与接口继承的区别,继承是真正的合并成一个类型,交叉是两种类型都能用

    interface jx5 {
    fn(s: string):void // 参数string
    }
    interface jx6 {
    fn(s: number):void // 参数number
    }

    type jx7 = jx5 & jx6
    const jx8: jx7 = {
    fn(a){}
    }
    jx8.fn('') // 此时参数两种类型均可

    // interface jx5_5 extends jx5 { // 同名方法参数冲出时,继承报错
    // fn(s: number): void
    // }
  • 泛型
    • 泛型就是通过类型变量,动态确定类型,类型变量类似函数的参数,以<a, b>形式传递。有函数泛型、接口泛型、泛型类
    • 泛型约束extends:泛型在未调用之前是不能确定具体类型的,所以所有的属性都无法访问,需要约束确定一定有的属性,才能访问
    • 泛型工具类型:默认提供的一些处理类型的方法
      • Partial 将所有属性变成可选
      • Readonly 将所有属性变成只读
      • Pick<props, p1 | p2> 获取某几个属性(p1、p2)形成新的类型
      • Record<p1 | p2, types> 构建一个对象类型,将p1、p2作为属性并统一属性的类型为types
    • 通常定义泛型变量
      • T(Type):表示一个 TypeScript 类型
      • K(Key):表示对象中的键类型
      • V(Value):表示对象中的值类型
      • E(Element):表示元素类型
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        47
        48
        49
        50
        51
        52
        53
        54
        55
        56
        57
        58
        59
        60
        61
        62
        63
        64
        65
        66
        67
        68
        69
        70
        71
        72
        73
        74
        75
        76
        77
        // 函数泛型
        function fn<T>(opt: T): T{ return opt } // T就是类型变量,自定义命名,<>接受类型参数
        fn<string>('') // 调用时传入类型参数,此时才确定了函数中T的具体类型
        fn('') // 当ts类型推论能自动推出类型时,可省略,不能推出时必须手动添加

        // 接口泛型
        interface inter<A> {
        name: A
        sayHi: () => A
        }
        let i: inter<string> = { // 接口类型传入
        name: '',
        sayHi: () => ''
        }

        // 泛型类
        class C<B> {
        name: B
        constructor(name: B) {
        this.name = name
        }
        }
        let u = new C<string>('ss')

        // 多个类型变量,想参数一样,逗号分隔
        function fn<T,U>(arr: [T,U]): [U, T] {
        return [arr[1], arr[0]]
        }
        fn(['1', 1]) // [1, '1']

        // 泛型约束
        function fn<T>(s: T): T{
        // s.length // 此时由于不确定具体类型,所有任何属性都无法访问
        return s
        }

        interface p {
        length: number
        }

        function fn<T extends p>(s:T):T { // 约束类型内至少包含p中定义的属性类型
        clg(s.length) // 此时能访问
        return s
        }

        function fn<types, K extends keyof types>(obj: types, k: K) {
        return obj[k]
        }
        let o = {name: '', age: 1}
        fn(o, 'name') // keyof 获取一个以key组成的联合-字面量类型:'name' | 'age'。因此第二个参数只能是name或age

        // 泛型工具类型
        interface inter {
        name: string
        age: number
        sex: number
        }

        type partial_v = Partial<inter>
        let p: partial_v = {} // 此时属性都是可选的,所以不写也不会报错

        type readonly_v = Readonly<inter>
        let p: readonly_v = { name: '', age: 1, sex: 0 }
        // p.name = 'ss' // 报错,只读

        type pick_v = Pick<inter, 'name'|'age'> // 获取两个属性形成新的类型
        let p: pick_v = { name: '', age: 1 } // 只有这两个属性

        type prt = 'name' | 'age'
        interface inter2 {
        value: string
        }
        type record_v = Record<prt, inter2> // prt作为对象的键,inter2规定键的类型统一为{value: string}
        let p: record_v = {
        name: { value: ''},
        age: { value: ''}
        }
  • 索引签名类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    // 不确定类型属性,但确定类型属性的类型,对象的属性是string,数组的属性类型是number
    interface o {
    [k: string]: string
    }
    let obj: o = { // 对象属性是string,随意定义不会报错
    a: '',
    b: '',
    c: ''
    }
    interface a<T> {
    [i: number]: T
    }
    let arr: a<number> = [1,2,3]
  • 映射类型
    • 基于旧类型生成新类型,简化操作,减少重复
    • in操作符将联合类型变为新类型的属性
    • keyof 将对象类型的属性变成联合类型
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      type l = 'name' | 'age'
      type ll = { // {name: string;age:string}
      [k in l]: string // 相当于遍历联合类型,将key统一设置类型
      }
      interface p {
      name: 'string'
      age: number
      }
      type pp = {
      [k in keyof p]: p[k] // p[k]索引查询类型,像对象一样访问属性的类型
      }
      type pi = p['name'] // 注意:索引查询结果是类型,所以得用type接受,否则报错
      type pis = p['name'|'age'|'sex'] // 同时查多个索引,返回去重的联合类型

      // 实现Partial泛型工具类型
      type myPartial<T> = {
      [k in keyof T]?: T[k]
      }
      type m_p = myPartial<p>

类型声明文件.d.ts

  • 为js项目提供类型声明
  • 统一声明 类型
  • declare关键字:为已有变量定义类型
  • 模块化导入导出
    1
    2
    3
    4
    5
    // 项目中已有未定义类型的变量a、b,用declare关键字为变量定义类型
    declare let a: string
    declare let b: number

    export {a, b} // 导出后需导入才能使用,如果不导出,ts会自动加载
  • Unkonwn类型
    同any,区别在于unknow类型不能赋值给其他类型,any类型可以
    1
    2
    3
    4
    5
    6
    7
    let a: string = ''
    let b: any = 1
    let c: unknown = true

    a = b // any类型能赋值给任何类型
    a = c // 报错,unknown类型只能赋值给同类型或any
    b = c // 可以
  • never类型
    类型表示的是那些永不存在的值的类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    // 抛出异常或根本就不会有返回值的函数  可以用never
    function throwErr(msg): never {
    throw new Error(msg)
    }
    function loop(): never {
    while(true) {}
    }

    // *** 可以利用 never 类型的特性来实现全面性检查 ***
    type foo = string | number
    function fn(opt: foo): void {
    if (typeof opt === 'string') {
    // 这里opt被收窄为string
    } else if (typeof opt === 'number') {
    // 这里opt被收窄为number
    } else {
    // 这里opt被收窄为never,因为按类型约定,不可能走到这里
    const check: never = opt
    // type foo = string | number | boolean
    // 此时如果foo 多了个boolean类型,此处opt被收窄为boolean,再赋给never类型的check就会报错,起到了全面性检查的作用
    }
    }

typeof 在ts中的使用

  1. 能像js一样判断数据类型;
  2. 在类型上下文中获取类型并使用
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    let obj = {
    x: 1,
    y: 1
    }
    typeof obj // object 同js里使用一样

    function fn (opt: typeof obj) {} // 此时,typeof处于类型上下文中,获取的是obj的类型{x: number; y: number},能够用来定义opt的类型

    let n: typeof obj.x = 10 // n的类型是number


ts有类型推论的能力

  • 根据声明自动推论类型;能用推论就用,省事才是王道;const 不会推论,而会形成字面量类型
    1
    2
    3
    4
    5
    6
    7
    8
    9
    // 类型推论
    let a = '' // 推论得到string类型
    a = 1 // 改变类型会报错

    let b // 没有赋值,没法推论,这种情况要手动定义类型
    b = 1
    b = ''

    function fn (n: number) { return n } // 自动推论函数返回值

类型兼容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// ts 是结构化类型系统(duck typing)即结构一样或兼容时,不区分类型或类名的不同

class Jr1 {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
}
class Jr2 {
x: number
y: number
constructor(x: number, y: number) {
this.x = x
this.y = y
}
}
const jr1: Jr1 = new Jr2(1,2) // 两个类的结构一样,名称不一样,但能相互赋值

// 兼容性:保证属性或参数能够被定义即可,允许多出未定义的属性或参数
class Jr3 {
x: number
constructor(x: number) {
this.x = x
}
}

const jr3: Jr3 = new Jr2(1,2) // Jr2类包含Jr3,虽然多出y属性,但能赋值给Jr3类型
console.log(jr3)

interface Jr4 {
y: number
}
const jr4: Jr4 = new Jr2(1,2) // 类和接口能相互兼容,接口之间的兼容规则和类一样
console.log(jr4)

type Jr5 = (x: number) => void
type Jr6 = (x: number, y:number) => void

let jr5: Jr5 = (x) => {}
let jr6: Jr6 = (x, y) => {}
jr6 = jr5 // 函数参数6包含5,能赋值
// jr5 = jr6 // 报错

参考

教程
最全TypeScript 入门基础教程,看完就会,了不起