likes
comments
collection
share

TypeScript 中的类型(上)

作者站长头像
站长
· 阅读数 29

前言

以下代码案例只是为了方便书写故有的地方存在使用 const 多次声明。

datatype

JS:

  • null、undefined
  • string、number、boolean
  • bigint、symbol
  • object(含 Function、Array、Date...)

TS:

  • 以上所有
  • 加上 voidneverenumunknownany
  • 再加上自定义类型 type、interface

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

JS 的类型:每一个值(数据)的类型

TS 的类型:一类数据的类型

JS 类型

null、undefined、1、2、3、4、5、a、b、c、[]、function(){}、new Date()...

TS 类型

  • 所有的数字包含在一起取一个名字叫 number
    • type number = 1 | 2 | 3 | 4 | 5 | 6 | 6.2 | 7|...
  • 所有的只有看起来像字符串的统一起来命名为 string
    • type string = a | b | c | d | fff | xxx | ...
  • type boolean = ture | false
  • type Object = 普通对象 | Array | Function | String | Number | Boolean | RegExp ...
  • 小写的 object 就是正常所理解的 object, 比如 数组、函数、对象 (小写 object 一般用的少)
  • ⚠️注意:这里的 | 读做

TS 为什么有两 string number boolean ?

const n = 9
const m = new Number(9)

它们的区别是:

当写 n = 9 它就是 9 (内存里面就是 0、1 存了 64 位)

当写 new Number(9) 的时候,会封装一个对象

{
  constructor: ...
  toFixed: ...
  toString: ...
  valueOf: function(){
    return 9
  }
}

当我 m.valueOf() 的时候才会返回一个真正的数字 9

包装对象

6..toFixed(2)   // '6.00'

为何 6 有一个 toFixed 方法, 6 不是由 0 和 1 表示的?那为何会有一个 toFixed 方法呢?

因为 JS 有一个包装对象的暗箱操作。

当它发现 6 没有 toFixed 的时候,它就会把变成 new Number(6),并删除旧的 6。

# 大概过程
let temp = new Number(6)
value = temp.toFixed(2)
删除 temp
value

总结

JS 不会直接使用 new Number(),它会在操作一个 number 的时候,由 浏览器/node 包装出一个 new Number() 来使用(永远都是暗箱操作的使用)。

JS 中的 Number、String、Boolean 只用于包装对象,正常开发者不用它们,在 TS 里也不用。

也就是说当我们在写 TS 的时候,永远都要写 小写的 number、string、boolean,不要写大写的。

用类型签名和 Record 描述对象

type Object 表示的范围太大了

const a: Object  = []
const b: Object  = () => 1
const c: Object  = /ab+c/

以上代码都不会报错,没有发挥出 TS 用来描述这个对象有哪些特性的功能,所以不用Object

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

  1. class/constructor 描述
  2. typeinterface 描述
// 在 TS 中以下有三种写法
type Person = {name: string; age: number}
type Person = {name: string, age: number}
type Person = {
  name: string
  age: number
}
// JS 中 就不可以写成以上的三种写法
const a:Person = {
  name: 'hone',
  age: 18
} 
// 以上代码少写/多些、类型不对都会报错、对象字面量只能指定知道的属性
// 经常会用到的一种索引签名的用法
// 对象的下标 k 必须是一个 string, 对象的 value 必须是一个 number
type A = {
  [k: string]: number
}

// 另一种写法
type A2 = Record<string, number>

// 以上 A 和 A2 所表达的含义是一摸一样的

// 还可以写的具体一点
type A3 = {
  name: string
  age: number
}

// 用法
const a: A = {
  name: 1,
  123: 6
}
// 在 JS 层面来说 123 最终会变成字符串
type A = {
  [k: symbol]: number
}

// JS 中 如果 symbol 作为 key 必须用🀄括号包起来
const a: A = {
  [s]: 1 
}

key 的类型可以不是 string, 可以是 number、symbol。

结论

由于 object 太不精确,所以 TS 开发者一般使用,索引签名Record 泛型 来描述普通对象

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

数组对象该怎么描述

type A = string[]
const a: A = ['h', 'i']

type B = number[]
const b: B = [1, 2, 3]

// 以上写法和下面等价
type A = Array<string>

type B = Array<number>
type D = [string, string, string]
const noError: D = ['加', '油', '冲']
const error: D = ['h', 'i'] // error: source has 2 elements but target requires 3
type E = [string, number]
const e:E = ['老李', 11]

type F = [string[], number[]]
const f: F = [['柴', '米', '油', '盐'], [1, 2, 3]]
const Two = [number, number] // 二元组
const Three = [number, number, number] // 三元组

二元组里面不能有三个元素,三元组里面不能有两个元素

结论

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

// 只含有一个元素的集合也是集合
// 当你把类型看成集合的时候以下写法是正确的
type A = [1, 2, 34]
const a: A = [1, 2, 4]

描述函数对象(这里先只介绍简单写法)

type FnA = (a: number, b: number) => number
// 下面代码不报错,因为 TS 可以认为是松散的类型检查(参数些少了是可以的,能少但不能多)
const a: FnA = () => {
  return 123 
}

// 类型推导:当我们在前面写了类型 (:FnA), 后面 (x, y) 就可以不用写了
const a: FnA = (x, y) => {
  return 123 
}

// 当调用的时候,就必须按照类型描述去传,不能多也不能少
a(1, 2)

type FnB = (x: string, y: string) => string

void

// 如果函数没有返回值 就可以写一个 返回值为 void, 不要写 undefined 和 null
type FnReturnVoid = (s: string) => void

// 一般不会写这种
type FnReturnUndefined = (s: string) => undefined

const v: FnReturnVoid = (s: string) => {
  console.log(s)
}  // 这个的返回值是 void

const u: FnReturnUndefined =(s: string) => {
  console.log(s)
  return undefined 
} // 这里不写 return undefined | null 就会报错,TS 不会自动 ruturn undefined 
// 声明一个 支持 this 的普通函数,你依然要使用箭头函数来声明它的类型
type Person = {
  name: string
  age: number
  sayHi: FnWithThis
}
type FnWithThis = (this: Person, name: string) => void

// 2. 你这个函数一般来说不能用箭头函数,只能用 function
const sayHi: FnWithThis = function(){
  console.log('hi ' + this.name)  // 打印出 hi frank
}

const x: Person = {
  name: 'frank',
  age: 18,
  sayHi: sayHi
}

// 1. 如果你的函数声明有 this ,你在调用这个函数的时候,你必须显示的传递 this
x.sayHi('hone')
// sayHi.call(x, 'hone')

结论

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

描述其他对象

其他对象一般直接用 class 描述

type A = object
const a: A =  ()=> 1
const b: A =  {}
const c: A =  []
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("xxx", 2)
const wm: WeakMap<{name: string}, number> = new WeakMap()
const s: Set<number> = new Set()
s.add(123)
const ws: WeakSet<string[]> = new Weakset()
const n: bigint = 100n  // >=es2020
const s: symbol = Symbol()
// DOM
const button = document.getElementById("xxx")
if(button) { button } // 这里就不为 null
// null 的集合只有 null
type A = null
const a: A = null

// undefined 的集合只有 undefined
type A = undefined
const a: A = undefined

any 和 unknown

any

如果 a: number | string 那么 a 可以等于 1 也可以等于 "x"。

如果范围再扩大一点 a: number | string | boolean

再大 a: number | string | boolean | {} ... 一直大到把所有的都包起来

那就可以用 any 表示, any 可以说是全知全能的类型。

unknown

unknowm 就是我有一个圈,我随便画在哪里,这个圈的大小不知道,圈的直径、范围不知道,随便丢进去,框到哪个算哪个。

const a: unknown = 1

// 这里不可以 .toFixed  因为它的类型是 unknown
// 虽然我在开始声明 a 的时候我告诉你是一个 1,但是我把它盖住了(被圈盖住了),你不能使用
// 不写 unknown 就可以使用以下 toFixed 方法
a.toFixed()

所以 unknown 就是我把这个类型盖住我不让你知道。

那为何要盖住呢?并不是故意要盖住的,因为很有可能这个 1 是我从远端得到的

// 有可能是从 ajax 得到的一个 1, 从一开始确实不知道是什么
// 我先盖这个地方,然后我从网络上来个一个值,放在这个盖的下面
const a: unknown = await ajax.get('/api/users')

// 一开始我不知道什么类型,你现在告诉我(你取的数据你知道类型)
;(a as number).toFixed()

所以 unknown 特别适合 你这个值是从外部获取的,没有办法提前知道 那我就盖住,你想用的时候,再翻起来,使用 as 断言

总结

any: 我什么都要,如果用 any 就没有机会去断言,想怎么写就怎么写,不会报错,全集,少了一次检查的机会。

unknown: 我先盖住,等要用的时候自己再去归一类(玩骰子猜大小),如果接错了就自己负责,未知集,一般说推荐使用 unknown,这样我就有一个机会去断言。

const b: unknown =  1;
(b as string).split(',') // 自己断言的出错就自己负责

never

never 里面什么都没有,空集,一个不包含任何元素的集合叫做 never。

空集用来做检查

type A= string & number // A 是 never
// 这个 never 不是用来声明的,而是用来推断的

type B = string | number | boolean
const a: B = ('hi' as any)
if (typeof a === 'string') {
  a.split('')
} if (typeof a === 'number') {
  a.toFixed(2)
} if (typeof a === 'boolean') {
  a.valueOf()
} else {
  console.log('never')
}