《二》TypeScript 中的基本类型

类型声明和类型检测:

在 TypeScript 中,通过类型声明来指定变量的类型,指定类型后,当为变量赋值时,TS 编译器会自动进行类型检测,检查值是否符合指定的类型,符合则赋值,否则报错。其中,使用 : 来指定变量的类型,: 前后有没有空格都可以;指定的类型可以称之为类型注解(Type Annotation)

语法为:var/let/const 变量名: 类型 = 变量值

TypeScript 对于很多类型的检测报不报错,取决于它的内部规则。TypeScript 版本也在不断地更新:在进行合理的类型检测的情况下,同时让 TypeScript 更好用,在它们之间寻求一个平衡点。

简而言之,类型声明给变量设置了类型,设置类型后会自定进行类型检测,使得变量只能存储某种类型的值。

let name: string = 'Lee' // 声明一个变量 name,同时指定它的类型为 string。
name = 'Mary' // 正确
name = 10 // 报错

TypeScript 中进行类型检测的时候,使用的是鸭子类型。
鸭子类型:如果一只鸟,走起来像鸭子,游起来像鸭子,看起来像鸭子,那么就可以认为它是一只鸭子。也就是说,只关心属性和行为,并不关心到底是不是是对应的类型。

class Person {
  constructor(public name: string, public age: number) {}
}

class Dog {
  constructor(public name: string, public age: number) {}
}

function showPersonInfo (p: Person) {
  console.log(p.name, p.age)
}
showPersonInfo(new Dog('旺财', 3)) // 正确。要求传入的实例对象是 Person 类,实际传入的实例对象是 Dog 类,但是因为它们有相同的属性,因此不会报错

类型推断:

在声明一个变量时,如果有直接赋值,TypeScript 会根据值的类型推断出类型注解,这就是类型推断。

因此,在开发过程中:

  1. 如果声明一个变量时直接对其进行赋值,可以省略类型注解。
  2. 通常情况下不需要明确函数的返回值的类型注解,TypeScript 会进行类型推断。

let 进行类型推断,推断出来的是通用类型;const 进行类型推断,推断出来的是字面量类型。

let name = 'Lee' //  根据值的类型推断出类型注解为 string
const num = 10 // 根据值的类型推断出类型注解为 10

类型别名:

类型别名用来给一个类型起个新名字。语法为 type 自定义类型名 = 类型。类型别名常用于联合类型。

type customType = 1 | 2 | 3 | 4 | 5
let  n1: customType
let  n2: customType
n1 = 1
n2 = 5

类型断言:

类型断言可以用来手动指定一个值的类型。语法为 值 as 类型 或者 <类型> 值

在 tsx 语法(React 的 jsx 语法的 ts 版)中必须使用 值 as 类型

请添加图片描述

在这里插入图片描述
此时,可以使用类型断言明确指定 imgEl 的类型为 HTMLImageElement。

let imgEl = document.querySelector('.img') as HTMLImageElement
imgEl.src = '' // 正确

TypeScript 允许类型断言由一种具体的类型断言为另一种不太具体的类型,或者由一种不太具体的类型断言为另一种具体的类型;但是不允许由一种具体的类型直接断言为另一种具体的类型,可以防止不应该发生的强制转换。

let num: number = 10
let num1 = num as string // 错误。从 number 类型断言为 string 类型,由一种具体的类型直接断言为另一种具体的类型
let num2 = num as any // 正确。从 number 类型断言为 any 类型,由一种具体的类型断言为另一种不太具体的类型
let num3 = num2 as string // 正确。从 any 类型断言为 string 类型,由一种不太具体的类型断言为另一种具体的类型

类型缩小(Type Narrowing):

把一个比较模糊的类型缩小为一个更加具体的类型,就叫做类型缩小。

常见的类型缩小的方式有:typeof、instanceof、switch case、平等缩小(=====!=!==)、in 等。

let id: number | string

console.log(id.length) // 报错。因为 id 可能是数字类型,也可能是 string 类型,是 number 类型的话是没有 length 属性的

if (typeof id === 'string') {
  console.log(id.length) // 正确。通过类型缩小将 id 的类型确定缩小为 string 类型
}

TypeScript 中的类型:

TypeScript 中有多种类型,还支持自定义拓展类型。

基本类型:

TypeScript 中的基本类型有:字符串类型 string、数字类型 number、布尔类型 boolean、undefined、null、数组类型、对象类型、元组类型 tuple、任意类型 any、未知类型 unknown、空值类型 void、没有值类型 never。

TypeScript 中的基本类型名是小写的。

字符串类型 string:

表示任意字符串。

let name: string = 'Tom'  // 正确
数字类型 number:

表示任意数字。

let num: number = 6.5  // 正确
布尔类型 boolean:

表示布尔值 true 或 false。

let flag: boolean = false // 正确
undefined:

表示 undefined 类型。

let u: undefined = undefined
null:

表示 null 类型。

let n: null = null
数组类型 array:

表示任意 JS 数组。语法为 类型[] ,或者也可以使用数组泛型 Array<类型>

let arr: string[]
arr = ['Lee‘, ’Mary‘] // 正确
arr = ['Lee‘, 18] // 错误

let arr1: Array<number> = [1, 1, 2, 3, 5] // 正确
对象类型 object:

表示任意的 JS 对象。但 object 类型不太实用,在开发中一般不使用。

// 在 JS 中一切皆对象
let obj: object
obj = {} // 正确
obj = function() {} // 正确

// 作为 object 对象类型的时候,既不能获取属性,也不能设置属性
let obj: object = {
  name: 'Lee'
} 
console.log(obj.name) // 报错

可以使用 {} 来代替,语法为:

{
	属性名: 类型, 
	属性名?: 类型,  // 可选属性。 ?  表示这个属性是可选的
	readonly 属性名: 类型, // 只读属性。只能在创建对象的时候被赋值,其他时候被赋值就会报错
	[index: string]: any, // 任意属性。这种写法可以称之为是索引签名,表示任意多个属性名的类型为 stirng,属性值的类型为 any 的属性。其中,index 只是一个标识符,可以任意命名;属性名的类型只能是 string 或者 number 其中一个。
}
// 直接赋值是,赋的值必须与 {} 中指定的属性名和类型一一对应,否则就会报错
let p: {name: string, age: number}
p = {name: 'Lee', age: 18}

// 可选属性:如果赋的值想要省略某个属性,可以在 {} 中指定的属性名后面加个 ?,表明这个属性是可选的
let p: {name: string, age?: number}
p = {name: 'Lee'}

// 只读属性
let p:  {readonly name: string, age: number}
p = {name: 'Tom', age: 25}
p.name = 'Lee' // 报错

// 任意属性:如果赋的值中除了要有 name,还想要有任意多个任意类型的属性,可以使用 [index: string]: any 来表示
let p: {name: string, [index: string]: any}
p = {name: 'Lee', age: 18, sex: '男'}

// 一旦定义了任意属性,那么其他属性中符合任意类型属性名类型的,属性值类型也必须符合。
let p:  {name: string, age: number, [index: string]: string}
p = {name: 'Tom', age: 25, gender: 'male'} // 报错。任意属性的属性名类型是 string,属性值类型是 string。但是age 的属性名类型是 string,属性值类型是 number

let p:  {name: string, age: number, [index: number]: string}
p = {name: 'Tom', age: 25, gender: 'male'} // 正确。任意属性的属性名类型是 number,属性值类型是 string。其他属性的属性名都是 string,不需要匹配任意属性的类型

对于对象的字面量赋值,在 TypeScript 中有一个现象,叫做严格的字面量赋值检测。每个对象字面量在第一次创建时都会被认为是新鲜的,TypeScript 对其进行严格的类型检测,必须完全满足类型要求;当其不再新鲜后,TypeScript 就不会对其进行严格的类型检测了。

type PersonType = {
  name: string,
  age: number,
}

const p1: PersonType = {
  name: 'Lee',
  age: 18,
  height: 1.88, // 报错。对于第一次创建的对象字面量,TypeScript 会将其标识为新鲜的,对其进行严格的类型检测,必须完全满足类型要求
}

// 此处是新鲜的,但是是在类型推断,并没有明确为其定义类型
const info =  {
  name: 'Lee',
  age: 18,
  height: 1.88, // 不报错
}
// 此处就不再是新鲜的了,不会对其进行严格的类型检测
const p2: PersonType = info // 不报错
Symbol 类型 symbol:

表示 Symbol 类型的值。

let s:symbol = Symbol()
元组类型 tuple:

元组是固定长度、固定类型的数组。语法为 [类型, 类型, ...]

let p: [string, number]

// 当直接对元组类型的变量进行初始化或者赋值的时候,需要提供所有元组类型中指定的项。
p = ['Lee', 25] // 正确
p = [25, 'Lee'] // 错误
p = ['Lee'] // 错误
p = ['Lee', 25, 10] // 错误

// 利用索引可以只赋值其中一项
p[0] = 'Lee' // 正确

// 元组是固定长度的
p[2] = 'Tom' // 错误

// 但是这种方法却不会报错,尽量不要使用
p.push('Mary') // 正确
p.push(10) // 正确
p.push(true) // 报错。当添加越界的元素时,它的类型会被限制为元组中每个类型的联合类型。

元组和数组的区别是:

  1. 数组中通常建议存放相同类型的元素;对于不同类型的元素更推荐存放在对象或者元组中。
  2. 元组中每个元素都有自己特定的类型,根据索引值获取到的值是可以确定其类型的;而数组中是无法确定其类型的。
任意类型 any:

表示任意类型的值。

如果无法确定一个变量的类型,或者变量的值的类型会发生改变,此时可以使用 any 类型。但是一个变量设置类型为 any 后相当于对该变量关闭 TypeScript 的类型检测,因此不建议使用 any 类型。

// 显式 any
let message: any
message = 'Lee' // 正确
message = 10 // 正确

// 隐式 any。声明变量但不赋值,如果此时不指定类型,则 TS 解析器会自动判断该变量的类型为 any。
let message
message = 'Lee' // 正确
message = 10 // 正确
未知类型 unknown:

表示类型安全的 any。

let message: unknown
message = 'Lee' // 正确
message = 10 // 正确

any 类型和 unknown 类型的区别是:在 any 类型的变量上直接做任何事都是合法的;在 unknown 类型的变量上直接做任何事都是非法的,必须要先类型缩小,经过校验,才能根据缩小之后的类型进行对应的操作。

let message: any = 'Lee'
let message1: string = message  // 正确。message 的类型是 any,可以直接赋值给其他任意类型的变量
	
let message: unknown = 'Lee'
let message1: string = message // 报错。message 的类型是 unknown,不能直接赋值给其他任意类型的变量

let message: unknown = 'Lee'
if (typeof message === 'string') {
  let message1: string = message // 正确。必须要先类型缩小,经过校验,才能根据缩小之后的类型进行对应的操作
}
let num: any = 10
console.log(num.length) // 正确
	
let num: unknown = 10
console.log(num.length) // 报错

let num: unknown = 10
if (typeof num === 'string') {
  console.log(num.length) // 正确。必须要先类型缩小,经过校验,才能根据缩小之后的类型进行对应的操作
}

let num: unknown = 10
if (typeof num === 'number') {
  console.log(num.length) // 报错。虽然经过了类型缩小,但没有进行对应类型的操作
}
空值类型 void:

表示空值,通常用来指定一个函数没有返回值。

function fn(): void {} // 正确

function fn1(): void {
	return 123  // 报错
}

如果一个函数没有返回值,那么它的返回值的类型就是 void。
请添加图片描述
如果一个函数的返回值类型是 void,TypeScript 允许它返回 undefined。

function fn():void {
  return undefined // 正确
}
没有值类型 never:

表示没有值。通常用来指定一个函数的返回值永远不会有任何结果,例如:抛出异常、死循环等。实际业务开发中基本不会用到,开发框架或者工具的时候才有可能会用到(可能会用于做一定的校验)。

function fn(): never {
	throw new Error(‘出错了’)  // 到这一行抛出异常,强行结束函数,函数的下一行不会执行到,函数永远执行不完
}

function fn1(): never {
	while(true) {
		console.log('死循环') // 死循环,函数永远执行不完
	}
}

void 类型和 never 类型的区别是:void 类型是函数没有返回值;never 类型是函数永远执行不完。

字面量类型 literal:

限制变量的值就是该字面量的值。

let num: 10
num = 10 // 正确
num = 11 // 报错
内置对象:

JavaScript 中有很多内置对象,它们可以直接在 TypeScript 中当做定义好了的类型。

内置对象是指根据标准在全局作用域上存在的对象。

  1. ECMAScript 的内置对象:ECMAScript 标准提供的内置对象有 Boolean、Error、Date、RegExp 等,可以在 TypeScript 中将变量定义为这些类型。
    let b: Boolean = new Boolean(1)
    let e: Error = new Error('Error occurred')
    let d: Date = new Date()
    let r: RegExp = /[a-z]/
    
  2. DOM 和 BOM 的内置对象:DOM 和 BOM 提供的内置对象有 Document、HTMLElement、Event、NodeList 等。
    let body: HTMLElement = document.body
    let allDiv: NodeList = document.querySelectorAll('div');
    document.addEventListener('click', function(e: MouseEvent) {})
    

联合类型和交叉类型:

TypeScript 的类型系统允许使用多种运算符,从现有类型中构建新的类型。

联合类型:

联合类型表示满足多种类型中的一种即可。使用 | 来连接多个类型,表示或的关系。

let union: string | number
union = 'Lee' // 正确
union = 10 // 正确

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,只能访问此联合类型的所有类型里共有的属性或方法。

function getString(value: string | number) {
    return value.toString() // 正确。toString() 是 string 和 number 的共有方法
}

function getLength(value: string | number) {
    return value.length // 报错。length 不是 string 和 number 的共有属性
}

function getLength(value: string | number) {
  if (typeof value === 'string') {
    return value.length // 正确。可以使用类型缩小来解决
  }
}
交叉类型:

交叉类型表示必须同时满足多种类型。使用 & 来连接多个类型,表示与的关系。

let cross: {name: string} & {age: number}
cross = {name: 'Lee‘, age: 18} // 正确
cross = {name: 'Lee'} // 报错

类型声明文件:

在 TypeScript 中,除了可以编写以 .ts 结尾的文件,还可以编写以 .d.ts 结尾的文件。

.d.ts 结尾的文件称之为类型声明文件,在这种文件中不写逻辑代码,只用来做类型声明,其中的类型声明全局都可直接使用。它的作用仅仅是用来告知 TypeScript 有哪些类型。

类型声明文件的分类:

类型声明文件可以分为以下几类:

  1. 内置的类型声明文件:是 TypeScript 自带的,内置了 JavaScript 运行时的一些标准化 API 的类型声明。例如:String、Date、Window、Document 等。

    TypeScript 将其放置在 lib.[something].d.ts 文件中。
    可以通过配置 tsconfig.json 配置文件中的 target 和 lib 选项来决定哪些内置类型声明是可以使用的。

  2. 外部定义的类型声明文件:一般来自第三方库,这些库中可能有类型声明文件,也可能没有。
    • 在库中已有类型声明文件:在 TypeScript 项目中引入这些库,库就可以直接使用。例如: axios。
    • 在库中没有类型声明文件:在 TypeScript 项目中引入这些库,库不可以直接使用,会报错。此时,就需要额外再去安装这些库的类型声明的依赖包。例如:React。
    • 在库中没有类型声明文件,并且也没有提供些库的类型声明的依赖包:在 TypeScript 项目中引入这些库,库不可以直接使用,会报错。此时,就需要开发者自己去写这些库的类型声明文件了。

      文件名可以任意命名。

      // 以 lodash 为例,lodash 有提供类型声明的依赖包,此处只是举例说明
      // 新建 types.d.ts 文件
      // 声明 lodash 模块
      declare module "lodash" {
      	export function join(...args: any[]): any
      }
      
  3. 开发者自己定义的类型声明文件。

类型声明文件中的语法:

类型声明文件中使用 declare 关键字来声明。

// 声明一个变量
declare const name: string
// 声明一个函数
declare function sum (num1: number, num2: number): number
// 声明一个类
declare class Person {
  constructor(public name: string, public age: number)
}
// 声明一个模块,是为了可以在 TypeScript 文件中作为一个模块 import 导入使用

// 声明 lodash 为一个模块,就可以在 TypeScript 中 import 导入使用了,否则将会报错
declare module "lodash" {
  // 在声明的模块中需要使用 export 关键字导出模块的属性和方法,就可以在外部通过模块名.属性/方法来调用
  export function join(...args: any[]): any
}

// 声明以 png 结尾的文件为一个模块模块,就可以在 TypeScript 中 import 导入使用了,否则将会报错
declare module "*.png"
// 声明一个命名空间
// 例如:通过 cnd 的方式引入了 jQuery,此时是无法在 TypeScript 中直接使用 $ 的。可以通过声明一个命名空间 $,然后在其中导出 jQuery 需要使用到的属性和方法,就可以在全局使用 $.ajax() 了。
declare namespace $ {
	export function ajax (settings: any): any
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值