TypeScript实战系列之合理运用类型

news2024/12/24 1:45:53

目录

  • 介绍
    • any 和 unknow
    • nerve 的用途
    • 断言
    • type 和 interface
    • declare 关键字的作用
    • 联合类型 和 类型守卫
    • 交叉类型

介绍

这篇主要介绍下ts 常用的基本类型和一些常用的技巧性技能

any 和 unknow

any 和 unknown 是两个类型关键字,它们用于处理类型不确定或未知的情况。
区别: unknown 只是在接受的传参的时候 忽略检查 当你需要使用的它的属性或者方法的时候 必须给它断言 要不然编译不通过,而any 在接受 和使用 都会去屏蔽 检验。

例子
场景: 需要定义一个函数给外部使用,但是外部的类型无法确定,要保证所有类型都能接受。但函数内部只对某一些类型处理。这时候就可以使用 any 和 unknow。

使用 any时,下面这段代码ts 不会给出编译错误提示,但实际运行会报错 。因为any 类型 在接受和使用的时候会屏蔽检查

function toFixedTwo(params:any){
    return params.toFixed(2)
}

toFixedTwo('100')

使用 unknow时,ts 在函数定义的时候就会给出编译错误提示!

function toFixedTwo(params:unknown){
    return params.toFixed(2)
}

在这里插入图片描述

结论:unknown 类型要安全得多,因为它迫使我们执行额外的类型检查来对变量执行操作。所以当我们并不知道第三方传过来的字段时,我们用unknow 去接受处理,尽量避免使用any。

最终如下:

function toFixedTwo(params:unknown){
    if(typeof params === "number"){
        return params.toFixed(2)
    }else {
        throw new Error("TypeError")
    }
}

nerve 的用途

never 是一个特殊的类型声明,它表示一个永不出现的值。never 单独使用的场景比较少,一般在封装工具类型时用的多(这个后续介绍类型体操中会使用的很频繁,运用了never特性。它可以)

下面列几个 never 的例子

function error(message: string): never { 
  throw new Error(message); 
}

function loop(): never { 
  while (true) { 
    // do something 
  } 
}

实际开发中的运用:

假如你针对某一个枚举值有处理逻辑

enum TestNeverEnum {
    FIRST,
    SECOND,
}

function getEnumValue(value: TestNeverEnum):string { 
    switch (value) { 
        // 这里 value 被收窄为 TestNeverEnum.FIRST
        case TestNeverEnum.FIRST: return "First case"; 
        // 这里 value 被收窄为 TestNeverEnum.SECOND
        case TestNeverEnum.SECOND: return "Second case"; 
        // returnValue 是 never 类型
        default: const returnValue: never = value; return returnValue; 
    } 
}

因为 case已经处理了全部的情况 所以不会走到default 所以 value 会被推断为 never

后面 假设增加的枚举的类型:

enum TestNeverEnum {
    FIRST,
    SECOND,
    THREE
}

function getEnumValue(value: TestNeverEnum):string { 
    switch (value) { 
        // 这里 value 被收窄为 TestNeverEnum.FIRST
        case TestNeverEnum.FIRST: return "First case"; 
        // 这里 value 被收窄为 TestNeverEnum.SECOND
        case TestNeverEnum.SECOND: return "Second case"; 
        // returnValue 是 never 类型
        default: const returnValue:never = value; return returnValue; 
    } 
}

在这里插入图片描述
如果你不继续增加case value 的类型就不会是never 就无法被赋值给never 就会编译不过了,可以避免漏写逻辑处理情况的发生。

断言

有时侯 通过内置的api获得的值类型可能 范围很大。但是实际你是知道具体类型的 你就可以使用断言来指定变量的类型。

例子:
场景1:

const myCanvas = document.getElementById("main_canvas")

在这里插入图片描述
返回的是 HTMLElement 类型 但是实际你是知道他是一个cavans 容器

const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement

在这里插入图片描述
就会有对应的代码提示了!

场景2:
你需要遍历某个object 类型时,编译会不通过!

let obj = {
    name:'kd',
    age:35
}

Object.keys(obj).forEach(item=>{
    console.log(obj[item]);
})

在这里插入图片描述
实际我们知道这段代码是没有问题的,Object.keys() 方法 返回的是一个 string[] 这时候就需要使用断言来告诉编译器它正确的类型。
有两种解决方法:

  1. 是缩小item 的类型范围 将 string 类型 变为 obj 所拥有key 的联合类型
let obj = {
    name:'kd',
    age:35
}

type ObjKeyType = keyof typeof obj

Object.keys(obj).forEach(item=>{
    console.log(obj[item as ObjKeyType]);
})

在这里插入图片描述
另一种写法:

let obj = {
  name: "kd",
  age: 35,
};

type ObjKeyType = keyof typeof obj;

(Object.keys(obj) as Array<ObjKeyType>).forEach((item) => {
  console.log(obj[item]);
});

2: 去扩大obj 的类型

let obj = {
  name: "kd",
  age: 35,
};


Object.keys(obj).forEach((item) => {
  console.log((obj as Record<string,any>)[item]);
});

总结:断言用于在代码中明确表达对某个条件的假设或要求。它允许你对类型进行额外的检查和限制,以确保代码在运行时的正确性。

type 和 interface

type 和 interface 是用于定义类型的两种不同方式。它们之间有以下区别

  1. 定义类型范围不同
    interface 只能定义对象类型或接口当名字的函数类型
    type 可以定义任何类型,包括基础类型 联合类型 交叉类型
interface PeopleType {
    age:number
    name:string
}

interface Run {
    (name:string):void
}

type People = {
    age:number
    name:string
}

type RunType = (name:string)=>void

type Num = number

type BaseType = string |number|Symbol

type AllType = {name:string}&{age:number}
  1. interface 可以继承 一个或者多个接口和类。type 无法继承,但是能使用交叉类型来实现部分继承
interface Animal {
    name:string
}

interface Size {
    long:number
}

interface Cat extends Animal,Size {
    jump:(arg:string)=>void
}

let cat :Cat = {
   name:'cat',
   long:100,
   jump(arg) {
       console.log(arg);
   },
}
  1. interface 可以合并声明 type 不行
interface People {
   name:string,
   address:string
}

interface People {
    age:number
}

let people: People = {
    name:'kd',
    address:'usa',
    age:35
}
  1. type还提供了很多的关键字使用,这是interface不具备的 ,比如in extends infer 关键字 (后续再介绍使用)

总结: 选择使用 type 还是 interface 取决于你的需求。如果你需要定义具体的类型并且不希望它被扩展,可以使用 type;如果你需要定义可扩展的接口或具有默认属性的类型,可以使用 interface;

declare 关键字的作用

declare 关键字的重要特点是,它只是通知编译器某个变量是存在的,不用给出具体实现。比如,只描述函数的类型,不给出函数的实现,如果不使用declare,这是做不到的。

场景1: 自己的ts项目使用外部库定义的函数,编译器会因为不知道外部函数的类型定义而报错,这时就可以在自己的脚本里面使用declare关键字,告诉编译器外部函数的类型。
一般来说第三方库都会有对应的d.ts 声明文件,而声明文件中就大量的使用了 declare 来声明类型。

例子:我从外部引入了 一个 js 作用就是 在window 对象上挂载 sayHello 方法,在代码中可以直接使用,但是ts 并不知道 会报错。这时候 declare 关键字就派上用场了。

window.sayHello = function (arg) {
    console.log('hello---'), arg;
}

sayHello('hello')

在这里插入图片描述
这时候在全局 .d.ts 文件中 使用declare 关键字 声明下

// vite-env.d.ts
declare function sayHello(arg: string): void;

这段代码 就相当于告诉ts 我全局有一个 sayHello 的方法, 编译就通过了 。

场景2: 使用declare关键字声明资源

例如,如果我们想在代码中使用 PNG(直接使用ts 会报找不到模块,因为ts 只认识),我们可以创建如下声明:

declare module '*.png' {
    const src: string;
    export default src;
}

场景3: 使用声明关键字进行全局增强

注意:在使用 global 全局类型时, ts 不会将d.ts 文件当做一个模块来看,而是一个全局文件,外部文件无法使用 global 下的类型。

//global.d.ts
declare global { 
    type Type1 = {...}
}


declare type Type2 = {}

//demo.ts
let a:Type1  //error 类型Type1不存在
let b:Type2  //success 因为这个文件中没有任何"import"它是一个全局文件

需要你使用 export 或者 import 关键字 来表明这是一个模块

export {}; 
declare global { 
    type Type1 = {...}
}

declare type Type2 = {}

这时候 Type1 可以使用, 但是这时候 Type2 了。因为 这时候 d.ts 文件已经是模块化了,外部无法自己使用 Type2 ,因为 Type1 挂在global 下 所以可以获取。

正常一般推荐 使用 全局类型 全部写到 global下

export {}; 
declare global { 
    type Type1 = {...}
    type Type2 = {}
}

联合类型 和 类型守卫

在 TypeScript 中,联合类型(Union Types)表示取值可以为多种类型中的一种。使用|分隔每个类型,其格式为:Type1|Type2|Type3。
例如:

type sortStatus = 0|1|2
type codeType = '200'|'400'|'500'

针对对象时使用联合类型时:
使用联合类型的变量 可以接收
1.联合类型中任意一种数据类型全部属性和方法
2.也可以是全量满足其中一种类型,再加上另一个类型的部分属性和方法

type Object1 = {
    name:string,age:number,address:string,height:number
}

type Object2 = {
    name:string,age:number,run:(arg:string)=>void
}

// 任意一种
let obj1 :Object1|Object2 = {
    name:'kd',
    age:35,
    address:'usa',
    height:208
}

let obj2 :Object1|Object2 = {
    name:'kd',
    age:35,
    run(arg) {
        console.log(arg);
    },
}

// 全量满足其中一种类型,再加上另一个类型的部分属性和方法
let obj3 :Object1|Object2 = {
    name:'kd',
    age:35,
    address:'usa',
    run(arg) {
        console.log(arg);
    },
}

我们可以同时接收 number 和 string 类型,但是再使用的时候,ts 并不知道变量类型是 string 还是 number 。

type Type1 = string|number

const one:Type1 = 'str'
const two:Type1 = 123
function handle(arg:Type1){
    arg.length;
    arg.toFixed(2)
}

在这里插入图片描述
校验过不了,这时候就需要使用类型守卫了。

先介绍下什么是类型守卫?
在 TypeScript 中,类型守卫可以用于在运行时检查变量的类型,
并在代码块内部将变量的类型范围缩小到更具体的类型。
类型守卫通常使用类型断言、类型谓词、typeof 操作符、instanceof 操作符或自定义的谓词函数来判断变量的具体类型,并根据判断结果收窄变量的类型范围。typeof 类型守卫允许使用 typeof 操作符来在代码中根据变量的类型范围进行条件判断。

改造后:

const isString = (arg:any):arg is string=>{
  return typeof arg === 'string'
}

function handle(arg:Type1){
    if(isString(arg)){
        arg.length;
    } else {
        arg.toFixed(2)
    }
}

或者 直接使用typeof

function handle(arg:Type1){
    if(typeof arg === 'string'){
        arg.length;
    } else {
        arg.toFixed(2)
    }
}

交叉类型

在 TypeScript 中交叉类型是将多个类型合并为一个类型。通过 & 运算符可以将现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。

同名基础类型属性的合并

interface X {
    c: string;
    d: string;
  }
  
  interface Y {
    c: number;
    e: string
  }

  type DD = X & Y

  let obj:DD = {
    d:'22',
    e:'1',
    c:''
  }

在这里插入图片描述
c不能同时为 string 和 number 所以就为 never

同名非基础类型属性的合并:

interface D { d: boolean; }
interface E { e: string; }
interface F { f: number; }

interface A { x: D; }
interface B { x: E; }
interface C { x: F; }

type ABC = A & B & C;

let abc: ABC = {
  x: {
    d: true,
    e: 'semlinker',
    f: 666
  }
};

在混入多个类型时,若存在相同的成员,且成员类型为非基本数据类型,那么是可以成功合并。

使用场景:
多个类型都有重复相同的字段时, 使用type定义 利用 交叉类型 来实现继承的效果

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1419106.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

羊奶温和无副作用,对五脏六腑有益

羊奶温和无副作用&#xff0c;对五脏六腑有益 羊奶一直以来都被视为一种高营养价值的饮品。与普通的牛奶相比&#xff0c;羊奶含有更多的维生素和矿物质&#xff0c;对人体的健康有着更多的益处。羊奶不仅温和无副作用&#xff0c;而且对五脏六腑都有着独特的滋补作用。 首先&…

day30_HTML

day25后几天为答疑和测试&#xff0c;第二阶段学习第一天是day30 在今日内容 0 复习昨日 1 本周安排 2 第二阶段介绍 3 HTML 0 复习昨日 1 本周安排 前面的Java知识 类,对象,属性,方法 String,日期操作,包装类操作 集合操作 本周 HTML 1天CSS 1天JavaScript 3天 前端知识比后…

OpenGL/C++_学习笔记(四)空间概念与摄像头

汇总页 上一篇: OpenGL/C_学习笔记&#xff08;三&#xff09; 绘制第一个图形 OpenGL/C_学习笔记&#xff08;四&#xff09;空间概念与摄像头 空间概念与摄像头前置科技树: 线性代数空间概念流程简述各空间相关概念详述 空间概念与摄像头 前置科技树: 线性代数 矩阵/向量定…

Linux系统——正则表达式

有一段时间本机访问量过高&#xff0c;如何查看日志提取出访问量前十的信息 1.使用提取命令&#xff08;cut、awk、sed&#xff09;提取出ip地址的那一列 2.使用sort按数字排序&#xff0c;将相同的地址整合到一起 3.使用uniq -c统计出数量 4.使用sort 数字 数字倒序排序 5.最…

[C语言][C++][时间复杂度详解分析]二分查找——杨氏矩阵查找数字详解!!!

一&#xff0c;题目 遇到的一道算法题&#xff1a; 1&#xff0c;已知有一个数字矩阵&#xff08;row行&#xff0c;col列&#xff09;&#xff0c;矩阵的每行 从左到右 递增&#xff0c;每列 从上到下 递增。 2&#xff0c;现输入一个数字 num &#xff0c;判断数字矩阵中…

防御保护常用知识

防火墙的主要职责在于&#xff1a;控制和防护 --- 安全策略 --- 防火墙可以根据安全策略来抓取流量之 后做出对应的动作 防火墙分类主要有四类&#xff1a; 防火墙吞吐量 --- 防火墙同一时间能处理的数据量多少 防火墙的发展主要经过以下阶段&#xff1b; 传统防火墙&#xf…

报错“MySql配置文件已损坏,请联系技术支持”的解决方法

目录 第一步 打开控制面板&#xff0c;选择管理工具&#xff0c;再选择事件查看器 第二步 在【应用程序】里找到这条报错&#xff0c;记下来文件内容。我自己的来源是“MsiInstaller” 第三步 winR组合键&#xff0c;输入regedit打开注册表 第四步 根据前面报错的文件名定位…

【Python笔记-设计模式】抽象工厂模式

一、说明 (一) 解决问题 抽象工厂是一种创建型设计模式&#xff0c;主要解决接口选择的问题。能够创建一系列相关的对象&#xff0c;而无需指定其具体类。 (二) 使用场景 系统中有多于一个的产品族&#xff0c;且这些产品族类的产品需实现同样的接口。 例如&#xff1a;有…

仅使用 Python 创建的 Web 应用程序(前端版本)第10章_订单列表

本章我们将实现订单列表页面。 完成后的图像如下。 创建过程与之前相同,如下。 No分类内容1Model创建继承BaseDataModel的数据类Order、OrderDetail2Service创建一个 OrderAPIClient3Page定义PageId并创建继承自BasePage的页面类4Application将页面 ID 和页面类对添加到 Mult…

春节寄快递贵?想要便宜寄快递?那是你没找到寄快递便宜的渠道!

春节将至&#xff0c;公司会发放一大批的年货礼品给员工们&#xff0c;来聊表这一年来的勤恳工作的心意。但是想要拿走这么多的年货&#xff0c;可不是一件容易的事情啊&#xff0c;这时候我们可以通过邮寄的方式把东西邮寄走&#xff0c;是不是省了很多事呢&#xff0c;不仅回…

Unity之动画和角色控制

目录 &#x1f4d5; 一、动画 1.创建最简单的动画 2.动画控制器 &#x1f4d5;二、把动画和角色控制相结合 &#x1f4d5;三、实现实例 3.1 鼠标控制角色视角旋转 3.2 拖尾效果 &#x1f4d5;四、混合动画 最近学到动画了&#xff0c;顺便把之前创建的地形&#xff0…

uniapp实现页面左滑右滑切换内容

uniapp uview&#xff1a;使用uniapp的swiper和uview的tabs标签组合实现 Tabs 标签 | uview-plus 3.0 - 全面兼容nvue的uni-app生态框架 - uni-app UI框架 vue <template> <view class"main"> <view class""> …

Spring中用Mybatis注解查询映射多个对象

1.映射写法如下 SelectProvider(type UserGroupMapper.class, method "getOrigins")Results({Result(property "id", column "id"),Result(property "groupId", column "groupId"),Result(property "resId&qu…

C#,打印漂亮杨辉三角形(帕斯卡三角形)的源代码

杨辉 Blaise Pascal 这是某些程序员看完会哭的代码。 杨辉三角形&#xff08;Yanghui Triangle&#xff09;&#xff0c;是一种序列数值的三角形几何排列&#xff0c;最早出现于南宋数学家杨辉1261年所著的《详解九章算法》一书。 欧洲学者&#xff0c;最先由帕斯卡&#x…

老龄化对投资意味着什么?

1月15日&#xff0c;国务院办公厅印发《关于发展银发经济增进老年人福祉的意见》从4个方面提出26项举措&#xff0c;为我国首个以“银发经济”命名的政策文件。 近期&#xff0c;国信证券分析师王开发布题为《银发经济再思考&#xff1a;老龄化对投资的影响》的报告&#xff0…

使用this调用兄弟构造器

在任意类的构造器中&#xff0c;都可以通过this(...)去调用该类的其他构造器。 假如说只给有参构造器传了两个参数&#xff0c;想让第三个参数有默认值&#xff0c;那么可以这样&#xff1a; 可以看出有一部分代码重复了&#xff0c;那么可以通过this()优化&#xff1a; 这样可…

202412读书笔记|《做自己的花》——走自己的路,成为自己的星星

202412读书笔记|《做自己的花》——走自己的路&#xff0c;成为自己的星星 《做自己的花&#xff08;微信读书联合出品&#xff09;》作者月芽&#xff0c;一个用画画和诗讲故事的插画师、诗人。 画风治愈&#xff0c;故事感强&#xff0c;擅长在童话般的故事中描绘现实温暖的心…

js实现贪吃蛇

文章目录 实现方法_11实现效果2 实现步骤2.1 移动场地2.2 游戏难度2.3 造蛇和食物2.4 蛇的移动2.5 产生食物的随机位置 3 全部代码 实现方法_21 实现效果2实现想法2.1 蛇的存储和显示2.2 蛇的移动(重难点)2.3 吃食物 3 完整代码 实现方法_1 1实现效果 2 实现步骤 html部分忽略…

酒鬼酒2024年展望:稳发展动能,迈入恢复性增长轨道

文 | 琥珀酒研社 作者 | 渡过 最近几个月来&#xff0c;白酒估值回落到近十年来低位&#xff0c;反映出了整个白酒行业的市场低迷和虚弱现状。不管是头部企业五粮液、泸州老窖&#xff0c;还是区域酒企口子窖、金种子酒等&#xff0c;最近都通过“回购”或“增持”&#xff0…