TypeScript中的类型

297 阅读5分钟

wallhaven-o5g6r7.png

JS和TS中有哪些数据类型

JS 的datatype

  • null
  • undefined
  • string
  • number
  • boolean
  • bigint
  • symbol
  • Object
    • 包含了 Array,Function,Date...等

TS 的datatype

  • 包含了JS的所有数据类型
  • void
  • never
  • enum
  • unknown
  • any
  • 自定义类型
    • type
    • interface

如何理解TS的数据类型?

JS的数据类型和TS的数据类型不一样,JS的数据类型指的是一个值的数据类型,TS的数据类型是从一个集合的角度去理解的

如下图所示:

1698294082676.png 1698294452376.png

图的上半部分:null undefined 1 2 3 a b c false 等等的每一个值它就是JS的数据类型。

图的下半部分:

1 | 1.1 | ... | 2 | 2.2 | ...

a | b | c | ...

所有的数字的集合,包括整数,小数,正负数等等,它称之为TS的number类型

所有字符串的集合,称之为 string 类型

等等...

所以得 从集合得角度理解TS的数据类型

如何在TS里描述对象的数据类型?

用索引签名或Record泛型来描述对象

type A = {
    [k: string]: number
}

type A2 = Record<string, number>

type A3 = {
    name: string
    age: number
}

用 [] 或 Array泛型来描述数组对象

思考:下面的数组类型描述是可以的吗?

type A = Array

是不可以的,TS会报错告诉你,泛型类型“Array<T>”需要1个类型参数

所以数组的类型如何描述呢?

  • 第一种
type A = string[]
const a:A = ['h', 'i']

type B = number[]
const b: B = [12, 34]

//========================
//下面的等价于上面的

type A = Array<string>
type B = Array<number>

  • 第二种
type D = [string, string, string]
const noError: D = ['哈', '哈', '哈']
// 下面的error这个会报错,提示D类型有三个参数,你只有两个
const error: D = ['h', 'i']

type E = [string, number]
const e: E = ['小明', 100]

type F = [string[], number[]]
const f: F = [['柴', '米', '油', '盐'], [1, 2, 3]]

结论:由于 Array太不精确,所以TS开发者一般用 Array<?> 或 string[] 或 [string, number] 来描述数组

描述函数对象

type FnA = (a: number, b: number) => number

// 如果类型有return,那么函数必须有return
// 参数有没有无所谓,但不能超过类型定义的参数数量
const haha: FnA = () => {
  return 1
}

// 调用的时候必须传和类型一样数量的形参
haha(1, 2)

问:那如果不想有返回值呢?

type FnA = (a: number, b: number) => void

const haha: FnA = (x) => {
}

问:那类型返回不写void,写undefined 行不行

答:不行,类型返回undefined,函数也要返回undefine

type FnA = (a: number, b: number) => undefined

const haha: FnA = (x) => {
    return undefined
}

问: 上面用的都是箭头函数, 类型是箭头函数, 调用也是箭头函数。那如果我想用普通函数,调用this,要怎么用呢?

  • 普通函数的调用

    • 普通函数的类型,也是要用箭头函数的
    • 现在,我想要有一个普通函数,并且调用函数的this
       type Person = { name: string; age: number; sayHi: FnWithThis }
       type FnWithThis = (this: Person, age: number) => void
            
       const sayHi: FnWithThis = function(age) {
            console.log('hi ' + this.name)
            console.log('age', age)
       }
    
       const x: Person = { name: 'huang', age: 18, sayHi: sayHi }
    
       x.sayHi(12)
       sayHi.call(x, 12)
    
    

总结: 由于 Function 太不精确,所以 TS 开发者一般用 () => ? 来描述函数

描述其他对象

其它的对象就是什么类型加什么类型,大体就是自己试试,不行会有报错,很明确的说明为什么不行

const d: Date = new Date()

const r: RegExp = /ab+c/
const r2: RegExp = new RegExp('ab+c')

const m: Map<string, number> = new Map()
m.set('key', 123)
const wm: WeakMap<{name: string}, number> = new WeakMap()

const s: Set<number> = new Set()
s.add(123)
const ws: WeakSet<string[]> = new WeakSet()

enum 的用法

何时用enum

举个例子:如下图所示

1698312123084.png

这是一个todo的案例,后端返回一个 status 字段, status字段有 4个值。1是todo, 2是done, 3是archived, 4是deleted。映射到前端就 1是未完成, 2是已完成, 3是归档, 4是不做了

所以前端需要定义一个 status 变量, 这个变量的值只能是上面定义的 1,2,3,4

此时就能用 enum

enum A {
    todo = 1,
    done,
    archived,
    deleted
}

// deno 后面开始没有等于某个值是因为,这样写,下面的值就相当于每个排列下来都加1
// 下面的写法同上
// enum A {
//    todo = 1,
//    done = 2,
//    archived = 3,
//    deleted = 4
//}

let status: A = 1

status = A.todo
status = A.done

  • 另一个就是在权限控制的时候,可以用enum

例如:

// 用二进制表示权限
enum Permission {
  None = 0,
  Read = 1 << 0, // 0001
  Write = 1 << 1, //0010
  Delete = 1 << 2, // 0100
  Manage = Read | Write | Delete, // 0111
}

type user = {
  permission: Permission;
};

const user1: user = {
  permission: 0b0001,
};

if ((user1.permission & Permission.Write) === Permission.Write) {
  console.log('有写的权限');
}
if ((user1.permission & Permission.Manage) === Permission.Manage) {
  console.log('有管理权限');
}

总结:平时除了这两种情况下,其他大多数情况下是不需要用到enum的

何时用enum会显得很呆

例如:

enum Fruit {
    apple = 'apple',
    banana = 'banana',
    orange = 'orange
}

let f = Fruit.apple
f = Fruit.orange

console.log(f)

既然值是字符串,那为什么不写成下面这样子呢?

type Fruit = 'apple' | 'banana' | 'orange'
let f: Fruit = 'apple'
f = 'orange'

console.log(f)

什么是Type, 何时用Type

type:类型别名 (Type Aliases)

作用:给其他类型取个名字

type Name = string
type FalseLike = '' | 0 | false | null | undefined
type Point = { x: number; y: number }
type Points = Point[]
type Line = [Point, Point]
type Circle = { center: Point; radius: number }
type Fn = (a: number, b: number) => number
type FnWithProps = {
    (a: number, b: number): number,
    prop1: number
}

自己写的程序大多时候都可以用Type(对内,自己用的)。要是写插件让别人用的,那就不建议用Type

问: 别名是什么意思?

看下面例子:

type x = string
type Y = X

上面的例子中Y是什么?

答案是Y 是string, 为什么不是X呢,X就是一个别名,把string 赋给了Y

什么是interface, 何时用interfacce

interface: 声明接口

作用: 描述对象的属性

interface Data { 
     [k: string]: string
}
interface Point {
    x: number;
    y: number;
}
interface Points extends Array<Point>{}
interface Fn {
   (x: number, y: number): number;
}
interface Data2 extends Data {}

type 和 interface 的区别

  • 区别1

    • interface是用来描述对象的
    • type则描述所有数据

    如下图:

image.png

  • 区别2

    • type只是别名

    • interface则是类型声明

          interface A {
              n: string
          }
      
          type B = A
      

      问: B是什么?难道跟type中的例子一样? 答案: B = A , 因为A是真实存在的类型,type只是类型别名,不是真实存在的

  • 区别3

    • Type不能重新赋值
        type D = string
        D = number
        // 这是错误的
    

    上面的例子中D = number是错误的,type是不能重新赋值的

    • interface可以自动合并
    interface A {
      n: string;
    }
    interface A {
      z: number
    }
    
    let B:A = {
      n: 'a',
      z: 1
    }
    
    

    interface的全局扩展,所有可以通过interface对类型进行扩展

    declare global {
        interface String {
            aaa(x: string): void
        }
    }
    const s = 'string'
    s.aaa('bbb')
    

    对外API 尽量用 interface ,方便扩展 对内API 尽量用 type, 防止代码分散