一,TS中的八种基础类型
// 字符串
let str: string = 'hello world'
// 数字类型
let num: number = 123
// 布尔类型
let bool: boolean = true
// null
let n: null = null
// undefined
let u: undefined
// 对象类型
let obj: object = {}
// bigint
let big: bigint = 100n
//symbol
let sym: symbol = Symbol()
二,void,never,any,unknown类型
1,void类型表示没有任何类型,不能直接赋值
let a: void
let b: number = a //报错 void不能赋值给number
这种没有什么意义 如果一个函数没有返回值 可以给他赋值为void
function fn(): void {
console.log('测试666')
}
2, never类型表示永不存在的值的类型
// 抛出异常
function error(): never {
throw new Error('error')
}
// 死循环
function loop(): never {
while (true) {
console.log('死循环')
}
}
3,any类型表示可以是任何类型(无敌 anyScript)
let num: number = 10
num = '123' //报错 不能将类型“string”分配给类型“number”
// 使用any之后
let num2: any = 10
num2 = '123' //可以
num2.a() //不报错
不知道什么类型的时候 想省心就能使用any
4,unknown类型与any类型差不多,所有类型都可以分配给unknown,但是把unknown类型赋值给其他类型会报错
// unknown可以接收任何类型
let num: number = 10
let un: unknown = num
num = un //报错 不能将类型“unknown”分配给类型“number”
三,数组跟函数类型定义
1,数组类型的定义
// 数组类型定义
const list1: number[] = [1, 2, 3, 4]
const list2: Array<number> = [1, 2, 3, 4]
const list3: [number, string, boolean] = [1, '2', true]
const list4: [{ name: string; age: number }] = [{ name: 'admin', age: 18 }]
const list5: Array<{ name: string; age: number }> = [{ name: 'admin', age: 18 }]
const list6: { name: string; age: number }[] = [{ name: 'admin', age: 18 }]
const list7 = [{ name: 'admin', age: 18 }] as { name: string; age: number }[]
interface ObjType {
name: string
age: number
}
const list8: ObjType[] = [{ name: 'admin', age: 18 }]
const list9: Array<ObjType> = [{ name: 'admin', age: 18 }]
2,函数类型定义
// 函数类型定义
const fn1 = (a: number, b: number): number => {
return a + b
}
const fn: (a: number, b: number) => number = (a, b) => {
return a + b
}
四,元组
数组有时候是同类型的值 但是有时候也会存在多种类型值的情况 定义数组不同类型称之为元组
// 数组同一类型
let arr: number[] = [1, 2, 3]
// 数组不同类型
// 元组定义
const arr2: [number, string, number] = [1, '2', 3]
五,可选类型
// 可选类型 ?表示可有可没有
const arr1: [number, string, number?] = [1, '2', 3] //不报错
const arr2: [number, string, number?] = [1, '2'] //不报错
六,交叉类型
// 交叉类型
let a: string | number = 1
a = '1' //不报错
a = 1 //不报错
a = true //报错
// 交叉类型
interface User {
name: string
age: number
}
interface Sex {
sex: string
}
// 相当于继承
let user: User & Sex = {
name: 'zhangsan',
age: 18,
sex: '男'
}
七,对象类型与索引签名
// 声明一个用户类型
interface User {
name: string
age: number
}
// 第一个用户 不报错
const user1: User = {
name: '张三',
age: 18
}
// 第二个用户 报错缺少age属性
const user2: User = {
name: '李四'
}
因为在User类型中声明了age 当我们不确定是否存在age的时候可以通过?可选来控制
// 声明一个用户类型
interface User {
name: string
age?: number //可选类型
}
// 第一个用户 不报错
const user1: User = {
name: '张三',
age: 18
}
// 第二个用户 不报错
const user2: User = {
name: '李四'
}
还有一种情况,在实际开发中不确定后端返回多少字段,比如
// 声明一个用户类型
interface User {
name: string
age?: number //可选类型
}
// 第一个用户 不报错
const user1: User = {
name: '张三',
age: 18,
address: '北京', //报错
desc: '我是一个描述' //报错
//很多不缺定属性....
}
这时候我们可以通过索引签名来解决问题
// 声明一个用户类型
interface User {
name: string
age?: number //可选类型
// 索引签名
[key: string]: any
}
// 第一个用户 不报错
const user1: User = {
name: '张三',
age: 18,
address: '北京', //索引签名不报错
desc: '我是一个描述' //索引签名不报错
//很多不缺定属性....
}
八,类型断言
类型断言可以用来手动指定一个值的类型,即允许变量从一种类型更改为另一种类型。
interface Obj {
name: string
}
// 让obj被断言成Obj类型
let obj = {} as Obj
// 这时obj已经被断言成Obj类型,所以name属性是存在的
obj.name
九,函数重载
函数重载是指函数约束传入不同的参数,返回不同类型的数据,而且可以清晰的知道自己传入的参数以及不同的结果
// 函数重载
// 首先定义了一个函数
// 接受三个参数 第一个参数是必须选择的 后续几个参数可选
function fn(n1: number, n2?: number, n3?: number, n4?: number) {
console.log(n1, n2, n3, n4)
}
// 以下几种调用方式都是正确的
fn(1)
fn(1, 2)
fn(1, 2, 3)
fn(1, 2, 3, 4)
现在我们希望能传四个参数但是不可以传递三个参数 我们就可以使用函数重载
// 函数重载
// 首先定义了一个函数
// 接受三个参数 第一个参数是必须选择的 后续几个参数可选
// 函数重载
function fn(n1: number): any //传递一个参数
function fn(n1: number, n2: number): any //传递二个参数
function fn(n1: number, n2: number, n3: number, n4: number): any //传递四个参数个参数
function fn(n1: number, n2?: number, n3?: number, n4?: number) {
console.log(n1, n2, n3, n4)
}
fn(1) //不报错
fn(1, 2) //不报错
fn(1, 2, 3) // 报错 没有需要 3 参数的重载,但存在需要 2 或 4 参数的重载。ts
fn(1, 2, 3, 4) //不报错
还可以用来实现传入类型返回类型限制
// 函数重载
// 需求 如果x是字符串y也必须是字符串
// 相反如果是number y也必须是number
function fn(x: string | number, y: string | number) {
console.log(x, y)
}
fn('1', '2') //不报错
fn(1, 2) //不报错
fn(1, '2') //不报错
要怎么实现
// 函数重载
// 需求 如果x是字符串y也必须是字符串
// 相反 如果是number y也必须是number
function fn(x: string, y: string): any
function fn(x: number, y: number): any
function fn(x: string | number, y: string | number) {
console.log(x, y)
}
fn('1', '2') //不报错
fn(1, 2) //不报错
fn(1, '2') //报错 没有与此调用匹配的重载
十,可调用注解
我们想为一个函数进行类型注解可以通过
type A = () => void
let a: A = () => {
console.log('a')
}
可调用注解的写法
type A = {
(): void
}
let a: A = () => {
console.log('a')
}
可以配合函数重载来限制类型重载的个数
type Fn = {
(x: string, y: string): any
(x: number, y: number): any
}
function fn(x: string, y: string): any
function fn(x: number, y: number): any
function fn(x: string | number, y: string | number) {}
let fn2: Fn = fn
定义了两个类型重载 这时候页面是正常的 如果我们删除一个就会报错
type Fn = {
(x: string, y: string): any
(x: number, y: number): any
}
function fn(x: string, y: string): any
function fn(x: string | number, y: string | number) {}
let fn2: Fn = fn //报错
也可以给函数注解属性
type Fn = {
(x: string, y: string): any
(x: number, y: number): any
}
function fn(x: string, y: string): any
function fn(x: number, y: number): any
function fn(x: string | number, y: string | number) {}
let fn2: Fn = fn
fn2.username //报错类型“Fn”上不存在属性“username”
为Fn注解添加username属性注解
type Fn = {
(x: string, y: string): any
(x: number, y: number): any
username?: string
}
function fn(x: string, y: string): any
function fn(x: number, y: number): any
function fn(x: string | number, y: string | number) {}
let fn2: Fn = fn
fn2.username //不报错
可调用注解配合函数可以实现多个函数注解 普通的函数写法只能写一个
// 只能声明一个
type Fn = (x: string, y: string) => any
// 声明多个
type Fn = {
(x: string, y: string): any
(x: number, y: number): any
username?: string
}
十一,枚举
在 TypeScript 中,枚举(Enum)是一种数据类型,用于定义一组命名的常量值。枚举可以帮助开发者更清晰地表达意图,提高代码的可读性和可维护性。
基本语法
enum Direction {
Up,
Down,
Left,
Right
}
// Direction 枚举定义了四个常量值:Up、Down、Left、Right。
// 默认情况下,枚举成员的值是从 0 开始递增的。
// 因此,Direction.Up 的值为 0,Direction.Down 的值为 1,以此类推。
// 在数字情况下我们是可以通过 Direction[1]的形式来反向获取枚举成员的名称的。
当我们修改了值为string之后 递增就会失效
enum Direction {
Up = 'up',
Down, //报错 枚举成员必须具有初始化表达式
Left, //报错 枚举成员必须具有初始化表达式
Right //报错 枚举成员必须具有初始化表达式
}
enum Direction {
Up = 'up',
Down = 'down',
Left = 'left',
Right = 'right'
}
常量枚举
const enum Direction {
Up = 'up',
Down = 'down',
Left = 'left',
Right = 'right'
}
代码编译后 枚举会被直接替换成常量 代码显示更整洁
十二,接口跟类型别名的区别
相同点
// 接口与类型别名都可以用来定义对象类型
// 都可以使用索引签名
// 也可以声明类型注解
// 同样支持可选参数 以及只读属性
interface User {
name: string
age?: number
[index: string]: any
(): void
}
type User2 = {
readonly name: string // 只读属性
age?: number // 可选参数
[index: string]: any
(): void
}
区别
// 区别1 接口只能声明对象类型{} 但是别名可以指定基础类型
type Str = string;
interface Str2 = string //报错 接口后面只能跟{}
// 区别2 接口具备合并功能 别名不能合并
interface IName {
name: string;
}
interface IName {
age: number;
}
// 两个相同类型名称 合并 现在的Iname 就是 {name:string,age:number}
const name: IName = {
name: 'zhangsan',
age: 18
}
// 区别3 接口可以继承 别名不能继承
interface A {
name: string;
}
interface B extends A {
age: number;
}
// B继承了A 所以可以拥有A的属性
const b: B = {
name: 'zhangsan',
age: 18
}
// 区别4 别名具备映射功能 接口不能
type Z = {
[P in 'name' | 'age']:string
}
// type Z = {
// name: string;
// age: string;
// }
十三,字面量类型与keyof
// 字面量类型
type Status = 'loading' | 'success' | 'success'
// 举例
let status: Status = 'loading'
let status2: Status = 'success'
let status3: Status = 'success'
let status4: Status = '6' // 报错 不能将类型“"6"”分配给类型“Status”
// keyof 关键字
interface User {
name: string
age: number
}
type UserKeys = keyof User // "name" | "age"
let userKey: UserKeys = 'name'
let userKey2: UserKeys = 'age'
let userKey3: UserKeys = 'sex' // 报错 不能将类型“"sex"”分配给类型“UserKeys”
十四,类型保护
案列一
// 案列一
const fn = (n: string | number) => {
console.log(n.length) // 报错 类型“number”上不存在属性“length”
}
解决方法1 可以通过断言来欺骗编译器 这就是一个字符串
const fn = (n: string | number) => {
console.log((n as string).length) // 问题解决 断言之后就相当于告诉编译器这是String
}
但是不推荐因为 我们在接收参数的时候是允许传入数字的
解决方法2 使用typeof增加一层判断
const fn = (n: string | number) => {
if (typeof n === 'string') {
console.log(n.length) // 进入到这个判断里的只能是字符串 问题解决
}
}
案例二
// 案列二
const fn = (obj: { name: string } | { age: number }) => {
console.log(obj.name) // 报错 类型“{ name: string; } | { age: number; }”上不存在属性“name”
}
因为可能是只传入了一个age
解决方法 我们一样可以增加一层判断来进行类型保护
const fn = (obj: { name: string } | { age: number }) => {
if ('name' in obj) {
console.log(obj.name) // 问题解决 如果name in obj 代表传入的就是第一个类型
}
}
案列三
// 案例三
class Dog {
name = '旺财'
}
class User {
phone = '123456'
}
// 参数接收一个类
const fn = (c: Dog | User) => {
console.log(c.phone) // 错误 因为可能传入的是狗的类 所以没有电话
}
解决办法 使用instanceof 来进行类型保护
// 类型保护
// 案例三
class Dog {
name = '旺财'
}
class User {
phone = '123456'
}
// 参数接收一个类
const fn = (c: Dog | User) => {
if (c instanceof Dog) {
console.log(c.name)
} else {
console.log(c.phone)
}
}
案例四
封装方法类型保护失效问题
// 案例四
// 封装函数
const typeFn = (n: string | number) => {
return typeof n === 'string'
}
const fn = (n: number | string) => {
if (typeFn(n)) {
console.log(n.length) // 报错 类型“string | number”上不存在属性“length”。 类型保护失效
}
}
解决办法使用 is类型谓词 来进行类型保护
// 封装函数 使用is类型谓词
const typeFn = (n: string | number): n is string => {
return typeof n === 'string'
}
const fn = (n: number | string) => {
if (typeFn(n)) {
console.log(n.length) // 问题解决
}
}
十五,泛型
定义函数接口或者是类的时候 未指定参数类型 希望在运行时传入才能确定
基本使用
// 泛型
// 定义一个基本泛型 T是参数名称随便起
type A<T> = T
// 传递了string 就是 string类型
const a: A<string> = '123'
// 传递了number 就是 number类型
const b: A<number> = 123
// 传递了boolean 就是 boolean类型
const c: A<boolean> = true
如果不传
// 如果不传
const d: A = '123' // 报错 泛型类型“A”需要 1 个类型参数
可以给泛型定义默认值
// 可以传递默认值 T = string
type A<T = string> = T
// 传递了string 就是 string类型
const a: A<string> = '123'
// 传递了number 就是 number类型
const b: A<number> = 123
// 传递了boolean 就是 boolean类型
const c: A<boolean> = true
// 如果不传
const d: A = '123' // 问题解决 不传默认字符串
函数使用
// 函数中使用
function fn<T>(n: T) {
console.log(n)
}
fn<string>('123')
fn<number>('123') // 报错 应该传number
函数与接口进行结合
// 函数与接口结合使用
// 定义一个接口 参数由泛型决定
interface A<T> {
(n: T): void
default?: T
}
// 当泛型传递为number时 default为number
let fn: A<number> = n => {
console.log(n)
}
fn.default = 1
let fn2: A<string> = n => {
console.log(n)
}
// 当泛型传递为String时 default为String 再赋值数值报错
fn2.default = 1 // 报错 不能将类型“number”分配给类型“string”
类中使用
// 在类中使用
class Dog<T> {
name: T // 报错 属性“name”没有初始化表达式,且未在构造函数中明确赋值
}
类中需要给值赋予初始值 可以使用 ! 非空断言解决
// 在类中使用
class Dog<T> {
name!: T // 解决
}
const dog = new Dog<string>()
dog.name = '小黑'
dog.name = 123 // 报错 因为泛型传递的是字符串
类继承
// 在类中使用
class Dog<T> {
name!: T
}
const dog = new Dog<string>()
// dog2继承dog1 泛型传递字符串
class Dog2 extends Dog<string> {
age!: number
}
const dog2 = new Dog2()
dog2.name = '小黑'
dog2.age = 2
// 泛型传递字符串 赋值number 报错
dog2.name = 123 // 报错
泛型约束
// 泛型约束
function fn<T>(n: T) {}
// 不传递泛型的时候 会自动根据传递的参数进行推导 所以传什么都不报错
fn(123) //不报错
fn('111') //不报错
进行约束
// 泛型约束
// 创建一个别名
type A = string
// extends进行泛型约束
function fn<T extends A>(n: T) {}
fn(123) //报错 约束了类型 为字符串
fn('111') //不报错
// 泛型约束
// 也可以要求参数是一个包含length属性的类型
type A = {
length: number
}
// extends进行泛型约束
function fn<T extends A>(n: T) {}
fn([]) //不报错
fn('111') //不报错
fn(1) //报错 number不具备length
十六,类型兼容性
// 类型兼容性
let a: { name: string } = { name: 'admin' }
let b: { name: string; age: number } = { name: 'admin', age: 18 }
a = b // 赋值可以 b中包含a中属性,所以可以赋值
b = a // 报错,a中缺少b中属性,所以不能赋值
const fn = (a: { name: string }) => {}
fn({ name: 'admin', age: 18 }) // 报错
// 根据变量 类型兼容性
let c: { name: string; age: number } = { name: 'admin', age: 18 }
fn(c) // 不报错
还有很多东西 懒得记了. . . . . .