重学TS --- 数据类型

316 阅读10分钟

基本使用

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

// 定义变量
const name: string = 'Klaus'

// 定义变量后在赋值
let age: number
age = 23

console.log(name, age)

// 默认情况下,所有的js文件都会在一个作用域下进行解析
// 如果需要将某一个js文件作为模块,来开启自己的作用域
export {}
// number就是typescript中的类型声明(类型注解)
// 声明(定义)num的类型为number类型
let num: number

num = 123 // success
num = '123' // error

数据类型

number

和JavaScript一样,TypeScript里的所有数字都是浮点数。 这些浮点数的类型是number

除了支持十进制和十六进制字面量,TypeScript还支持ECMAScript 2015中引入的二进制和八进制字面量。

let num: number

num = 123 // 十进制
num = 0b1111011 // 二进制
num = 0o173 // 八进制
num = 0x7b // 十六进制

boolean

最基本的数据类型就是简单的true/false值,在JavaScript和TypeScript里叫做boolean

let bool: boolean

bool = true
bool = false

string

JavaScript程序的另一项基本操作是处理网页或服务器端的文本数据。

像其它语言里一样,我们使用string表示文本数据类型。

和JavaScript一样,可以使用双引号(")或单引号(')以及模板字符串表示字符串。

let str: string

str = 'Klaus' // 单引号
str = "Klaus" // 双引号

// 直接赋值的时候 ,可以省略类型声明
// ts的类型检测系统会自动对类型进行推断
let username = 'Klaus'

str = `hello ${ username }` // 模板字符串

array

TypeScript像JavaScript一样可以操作数组元素。

// 定义方式1 --- 在元素类型后面接上[],表示由此类型元素组成的一个数组
let arr: number[]
arr = [1, 2, 3]

// 定义方式2 --- 泛型 --- Array<元素类型>
let array: Array<number | string>
array = [1, 'Klaus']

// 前边的括号不能省略 --- 提升运算符优先级
let arr2: (string | number)[]

typle

元组类型允许表示一个已知元素数量和类型的数组,各元素的类型不必相同

元组是个数确定, 且各个位置上元素类型确定的特殊数组

// 对应位置元素类型不对,和元素个数不对,编译都是通不过的
let typle: [number, string] = [23, 'Klaus']

console.log(typle[0]) // => 'Klaus'
console.log(typle[1]) // error

enum

enum类型是对JavaScript标准数据类型的一个补充。 像C#等其它语言一样,使用枚举类型可以为一组数值赋予友好的名字

// 定义枚举使用关键字 enum
// 枚举约定俗成 枚举名 首字母需要大写
enum Enum {
  RED,
  GREEN,
  BLUE,
  GRAY
}

// 通过别名获取枚举值
console.log(Enum.RED) // => 0
console.log(Enum.BLUE) // => 2

// 通过枚举值获取别名
console.log(Enum[0]) // => 'RED'
console.log(Enum[2]) // => 'BLUE'
// 默认情况下,从0开始为元素编号。 你也可以手动的指定成员的数值
// 或者,全部都采用手动赋值
enum Enum {
  RED,
  GREEN = 3,
  BLUE,
  GRAY = 8
}

console.log(Enum.RED) // => 0
console.log(Enum.GREEN) // => 3
console.log(Enum.BLUE) // => 4
console.log(Enum.GRAY) // => 8

any

any表示的是任意类型,也就是没有类型,不经过类型检测

可以认为any是TS中的顶层数据类型

不推荐使用,能不用就不用

let variable: any = 123
variable = 'Klaus'
let arr = [] // 此时arr的数据类型被推断为any[]

unknown

JS是弱数据类型语言,我们在写应用的时候可能会需要描述一个我们还不知道其类型的变量

这些值可以来自动态内容,例如从用户获得,或者我们想在我们的 API 中接收所有可能类型的值。

在这些情况下,我们想要让编译器以及未来的用户知道这个变量目前是无法被确定的

此时就可以设置这些变量的类型为unknown

unknown可以被视为是TS中的底层数据类型

let notSure: unknown = 4;
notSure = "maybe a string instead";
notSure = false;

any vs unknown

如果将一个变量设置为any类型,可以对any类型的变量进行任何的操作,包括获取不存在的属性、方法

可以一个any类型的变量赋值任何的值,比如数字、字符串的值

可以认为any是直接忽略了TS的类型检测机制

在对现有代码进行改写的时候,any类型是可能是有用的,它允许你在编译时可选择地包含或移除类型检查。

当一个变量只是定义,没有类型注解也没有赋值的时候,变量的默认类型就是any

let foo: any = 123
foo = 'Klaus'
foo = true

console.log(foo.length) // success
console.log(foo()) // success

而对于unknown而言,表示的是暂时不知道变量的类型,但是并不希望该变量脱离TS的类型检测系统

所以anyunknown的最大区别是:

  • any类型的值可以赋值给其它任何的数据类型
  • unknown类型的值只能赋值给any或unknown类型的变量
let foo: unknown
let foz: any

let num: number = foz // any -> number  --- success
num = foo  //  unknown -> number --- error

let str: string = foz // any -> string --- success
str = foo  //  unknown -> string --- error

在实际开发场景中,我们可以结合unknown类型保护一起使用

function foo(v: unknown): number | undefined {
  if (typeof v === 'string') {
    return v.length
  }

  if (typeof v === 'number') {
    return v.toString().length
  }
}

void

void刚好和any相反

void表示没有任何的值

一般使用于函数没有任何返回值的时候,这个函数的返回值类型就是void

在JS中默认情况下,函数没有返回值的时候,默认会返回undefined

所以在TS中,undefinedvoid的子类型

在没有开启strictNullCheck的时候,null也可以被视为void的子类型

function sum(num1: number, num2: number): void {
  console.log(num1 + num2)
}

sum(22, 45)

null 和 undefined

// undefined类型只有一个值 undefined
const un: undefined = undefined

// null类型只有一个值 null
const n: null = null

在没有开启strictNullCheck的时候,unedfinednull是其它任意类型的子类型,可以赋值给其它任意类型

let num: number = 23
num = null // success
num = undefined // success

但是如果开启了strictNullCheck,那么null和undefined只能赋值给自己的对应类型和any类型的变量,是无法赋值给其它类型的

// null 和 undefined 类型在定义的时候,最好需要加上类型注解,而非是使用类型推断 
// 因为在TS中null 和 undefined 可以被认为是任何类型的子类型 
// 因此在这里进行类型推断的时候,n 和 un 的类型会被推断为 any 
let n = null 
let un = undefined 
n = 'Klaus' // success 
un = 'Klaus' // success

never

never表示的是那些永不存在的值的类型,

一般用在函数抛出了异常或死循环的函数的返回值中

let fun = (): never => {
  throw new Error('error message')
}
let fun = (): never => {
  while(true) {}
}

never是其它任意类型(包括null 和 undefined)的子类型

所以never类型的变量可以赋值给其它任何类型的变量

// 这个IIFE无法正常执行完毕,所以neverVariable的类型为never
const neverVariable = (() => {
  throw new Error('error')
})()

const num: number = neverVariable // success
neverVariable = 123 // error

never类型的值可以赋值给never类型的变量

即使是any类型的值,也不能赋值给never类型的变量

let neverVariable = (() => {
  throw new Error('error')
})()

let variable: any 
neverVariable = variable // error
// never类型的应用场景

function handleMessage(message: number | string) {
  switch (typeof message) {
    case 'number':
      console.log('number 处理逻辑')
      break

    case 'string':
      console.log('string 处理逻辑')
      break
  }
}

// ------------------------------------------------
// 此时如果为参数message 添加一个新的类型boolean,但是不添加对应的处理逻辑
function handleMessage(message: number | string | boolean) {
  switch (typeof message) {
    case 'number':
      console.log('number 处理逻辑')
      break

    case 'string':
      console.log('string 处理逻辑')
      break
  }
}

// ts不会报错,因为message此时可以传递的数据类型中存在boolean类型
// 但是handleMessage这个函数中,没有对参数为boolean的时候进行逻辑处理
// 显然这个函数在实际执行的时候是没有意义的
handleMessage(true)

// ------------------------------------------------
// 因此我们可以对这个函数进行相应的修改

function handleMessage(message: number | string) {
  switch (typeof message) {
    case 'number':
      console.log('number 处理逻辑')
      break

    case 'string':
      console.log('string 处理逻辑')
      break

    default:
      // 如果之前所有的类型都处理完成了,那么也就不会进行default
      // 所以此时message的类型就是never
      // 但是如果此时有类型没有处理完全,如刚刚例子中的boolean类型没有处理的时候
      // message就是boolean的值,此时ts就会报错
      const foo: never = message
  }
}

object

function print(o: object): void {
  console.log(o)
}

print({
  name: 'Klaus',
  age: 23
})

Object类型的变量只是允许你给它赋任意值 - 但是却不能够在它上面调用对象自身特有的方法,即便它真的有这些方法

这是因为ts需要先编译后成为js才可以交给浏览器去解析和执行,那么也就意味着ts校验的时候是只看类型不看值的

因此使用object作为变量的类型的时候,只能调用对象的通用方法(也就是定义在Object.prototype上的方法)

是没有办法调用对象的特有属性或方法,如果的确需要调用,推荐使用接口来描述一个对象,而不是使用object类型

let o: object = {
  runing() {
    console.log('runing')
  }
}

o.running() // error
o.toString() // success

Symbol

 // symbol --- 符号 --- 独一无二的值 
 let sym1: symbol = Symbol('symbol') 
 let sym2: symbol = Symbol('symbol') 
 console.log(sym1 === sym2) // => false

类型断言

TS中的强制类型转换,只不过这是对类型的强制转换

所以类型断言没有运行时的影响,只是在编译阶段起作用

如果在实际场景中,TS的类型推断系统推测出的数据类型是错误的,

那么我们就可以使用类型断言来手动对数据类型进行强制转换

TypeScript只允许类型断言转换为 更具体(父类转换为子类) 或者 不太具体 (转换为any或者unknown)的类型版本,此规则可防止不可能的强制转换

function getLength(v: string | number): number {
  // 类型断言方式1 -- 泛型 在JSX中不支持
  if ((<string>v).length) {
    // 类型断言方式2 -- as关键字
    return (v as string).length
  } else {
    return v.toString().length
  }
}

console.log(getLength(123)) // => 3
console.log(getLength('Klaus')) // => 5

当然上述例子,也可以使用类型保护(类型缩小)的方式来解决

function getLength(v: string | number): number {
  // v的类型不是string 就是 number
  if (typeof v === 'string') {
    return v.length
  } else {
    // 之前已经是string类型了
    // 那么执行到else语句中的v的类型一定是number
    return v.toString().length
  }
}

但是在某些特殊的场景下,我们需要在两个无法的数据类型之间进行类型转换(不推荐)

   const str = 'Kluas' 
   // 类型只能是相近类型之间才可以进行转换 
   // const num: number = str as number // error 
   
   // 使用any或者unknown作为中转类型 
   let num = str as any as number 
   num = str as unknown as number

类型注解的首字母是否需要大写

NumberStringBooleanSymbol 以及 Object 这些类型和numberstringbooleansymbol 以及 object 这些类型是完全不一样的

NumberStringBooleanSymbol 以及 Object 这些类型是对应数据类型的包装类类型

相对地,我们应该使用 numberstringbooleanobjectsymbol来描述基本的数据类型

const num: number = 123 // num是number类型
const num1: Number = 123 // num是number对应的包装类Number类型
const o: Object = 4 // success
const obj: object = 4 // error