Typescript基础知识(类型拓宽、类型缩小)

news2024/9/30 17:25:39

系列文章目录

引入一:Typescript基础引入(基础类型、元组、枚举)
引入二:Typescript面向对象引入(接口、类、多态、重写、抽象类、访问修饰符)
第一章:Typescript基础知识(Typescript介绍、搭建TypeScript环境、基本数据类型)
第二章:Typescript常用类型(任意值any、数组Array、函数Function、元组Tuple、类型推论、联合类型)
第三章:Typescript基础知识(类型断言、类型别名、字符串字面量类型、枚举、交叉类型)
第四章:


文章目录

  • 系列文章目录
    • 一、类型拓宽
      • 1.1 什么是类型拓宽
      • 1.2 如何控制类型拓宽
        • 1.2.1 const 控制类型拓宽
        • 1.2.2 提供显式类型注释
        • 1.2.3 使用 const 断言
    • 二、类型缩小


一、类型拓宽

1.1 什么是类型拓宽

所有通过 let 或 var 定义的变量、函数的形参、对象的非只读属性,如果满足指定了初始值且未添加类型注解的条件,那么它们推断出来的类型就是指定的初始值字面量类型拓宽后的类型,这就是字面量类型拓宽。(第三章文章有提及过一嘴)

下面我们通过字符串字面量的示例来理解一下字面量类型拓宽:

  let str = 'this is string'; // 类型是 string
  let strFun = (str = 'this is string') => str; // 类型是 (str?: string) => string;
  const specifiedStr = 'this is string'; // 类型是 'this is string'
  let str2 = specifiedStr; // 类型是 'string'
  let strFun2 = (str = specifiedStr) => str; // 类型是 (str?: string) => string;

实际上,除了字面量类型拓宽之外,TypeScript 对某些特定类型值也有类似类型拓宽的设计,比如对 null 和 undefined 的类型进行拓宽,通过 let、var 定义的变量如果满足未显式声明类型注解且被赋予了 null 或 undefined 值,则推断出这些变量的类型是 any:

  let x = null; // 类型拓宽成 any
  let y = undefined; // 类型拓宽成 any

注意:在严格模式下,一些比较老的版本中(2.0)null 和 undefined 并不会被拓宽成“any”。

1.2 如何控制类型拓宽

  • 案例一

我们先来看一个示例,如下代码所示:

interface Vector {
  x: number;
  y: number;
  z: number;
}

function getComponent(vector: Vector, axis: "x" | "y" | "z") {
  return vector[axis];
}

let x = "x";
let vec = { x: 1, y: 2, z: 3 };
getComponent(vec, x); // Error:类型“string”的参数不能赋给类型“"x" | "y" | "z"”的参数。

在这里插入图片描述
上述代码中,为什么会出现错误呢?通过 TypeScript 的错误提示消息,我们知道是因为变量 x 的类型被推断为 string 类型,而 getComponent 函数它的第二个参数有一个更具体的字面量类型。这在实际场合中被拓宽了,所以导致了一个错误。

1.2.1 const 控制类型拓宽

如果用 const 而不是 let 声明一个变量,那么它的类型会更窄。事实上,使用 const 可以帮助我们修复案例一例子中的错误:

const x = "x"; // type is "x" 
let vec = { x: 10, y: 20, z: 30 };
getComponent(vec, x); // OK

因为 x 不能重新赋值,所以 TypeScript 可以推断更窄的类型,就不会在后续赋值中出现错误。
然而,const 对于对象和数组,仍然会存在问题

  • 案例二

以下这段代码在 JavaScript 中是没有问题的:

const obj = { 
  x: 1,
}; 

obj.x = 6; 
obj.x = '6';

obj.y = 8;
obj.name = 'semlinker';

但是在TypeScrip的环境中最后三局会出现错误:

const obj = { 
  x: 1,
};

obj.x = 6; // OK 


// Type '"6"' is not assignable to type 'number'.
obj.x = '6'; // Error

// Property 'y' does not exist on type '{ x: number; }'.
obj.y = 8; // Error

// Property 'name' does not exist on type '{ x: number; }'.
obj.name = 'semlinker'; // Error

上述代码中,对于 obj 的类型来说,它可以是 {readonly x:1} 类型,或者是更通用的 {x:number} 类型。当然也可能是 {[key: string]: number} 或 object 类型。TypeScript 的拓宽算法会将对象其内部属性赋值给 let 关键字声明的变量,进而来推断其属性的类型。因此 obj 的类型为 {x:number} 。你可以将 obj.x 赋值给其他 number 类型的变量,但是它还会阻止你添加其他属性。

1.2.2 提供显式类型注释

如果用给let声明的变量设置显示的类型注释,也可以修复案例一例子中的错误:

let x: "x" = "x"; // type is "x" 
let vec = { x: 10, y: 20, z: 30 };
getComponent(vec, x); // OK

基于字面量类型拓宽的条件,我们可以通过添加显示类型注解控制类型拓宽行为。

  const specifiedStr: 'this is string' = 'this is string'; // 类型是 '"this is string"'
  let str2 = specifiedStr; // 即便使用 let 定义,类型是 'this is string'

1.2.3 使用 const 断言

不要将const 断言与 let 和 const 混淆,后者在值空间中引入符号。这是一个纯粹的类型级构造。让我们来看看以下变量的不同推断类型:

// Type is { x: number; y: number; }
const obj1 = { 
  x: 1, 
  y: 2 
}; 

// Type is { x: 1; y: number; }
const obj2 = {
  x: 1 as const,
  y: 2,
}; 

// Type is { readonly x: 1; readonly y: 2; }
const obj3 = {
  x: 1, 
  y: 2 
} as const;

当你在一个值之后使用 const 断言时,TypeScript 将为它推断出最窄的类型,没有拓宽。当然你也可以对数组使用 const 断言:

/ Type is number[]
const arr1 = [1, 2, 3]; 

// Type is readonly [1, 2, 3]
const arr2 = [1, 2, 3] as const;

二、类型缩小

在 TypeScript 中,我们可以通过某些操作将变量的类型由一个较为宽泛的集合缩小到相对较小、较明确的集合,这就是类型缩小

比如,我们可以使用类型守卫将函数参数的类型从 any 缩小到明确的类型:

  let func = (anything: any) => {
    if (typeof anything === 'string') {
      return anything; // 类型是 string 
    } else if (typeof anything === 'number') {
      return anything; // 类型是 number
    }
    return null;
  };

同样,我们可以使用类型守卫将联合类型缩小到明确的子类型,示例如下:

{
  let func = (anything: string | number) => {
    if (typeof anything === 'string') {
      return anything; // 类型是 string 
    } else {
      return anything; // 类型是 number
    }
  };
}

我们也可以通过字面量类型等值判断(===)或其他控制流语句(包括但不限于 if、三目运算符、switch 分支)将联合类型收敛为更具体的类型,如下代码所示:

{
  type Goods = 'pen' | 'pencil' |'ruler';
  const getPenCost = (item: 'pen') => 2;
  const getPencilCost = (item: 'pencil') => 4;
  const getRulerCost = (item: 'ruler') => 6;
  const getCost = (item: Goods) =>  {
    if (item === 'pen') {
      return getPenCost(item); // item => 'pen'
    } else if (item === 'pencil') {
      return getPencilCost(item); // item => 'pencil'
    } else {
      return getRulerCost(item); // item => 'ruler'
    }
  }
}

在上述 getCost 函数中,接受的参数类型是字面量类型的联合类型,函数内包含了 if 语句的 3 个流程分支,其中每个流程分支调用的函数的参数都是具体独立的字面量类型。
那为什么类型由多个字面量组成的变量 item 可以传值给仅接收单一特定字面量类型的函数 getPenCost、getPencilCost、getRulerCost 呢?这是因为在每个流程分支中,编译器知道流程分支中的 item 类型是什么。比如 item === ‘pencil’ 的分支,item 的类型就被收缩为“pencil”。
事实上,如果我们将上面的示例去掉中间的流程分支,编译器也可以推断出收敛后的类型,如下代码所示:

  const getCost = (item: Goods) =>  {
    if (item === 'pen') {
      item; // item => 'pen'
    } else {
      item; // => 'pencil' | 'ruler'
    }
  }

一般来说 TypeScript 非常擅长通过条件来判别类型,但在处理一些特殊值时要特别注意 —— 它可能包含你不想要的东西!例如,以下从联合类型中排除 null 的方法是错误的:

const el = document.getElementById("foo"); // Type is HTMLElement | null
if (typeof el === "object") {
  el; // Type is HTMLElement | null
}

因为在 JavaScript 中 typeof null 的结果是 “object” ,所以你实际上并没有通过这种检查排除 null 值。除此之外,false的原始值也会产生类似的问题:

function foo(x?: number | string | null) {
  if (!x) {
    x; // Type is string | number | null | undefined
  }
}

因为空字符串和 0 都属于 false值,所以在分支中 x 的类型可能是 string 或 number 类型。帮助类型检查器缩小类型的另一种常见方法是在它们上放置一个明确的 “标签”:

interface UploadEvent {
  type: "upload";
  filename: string;
  contents: string;
}

interface DownloadEvent {
  type: "download";
  filename: string;
}

type AppEvent = UploadEvent | DownloadEvent;

function handleEvent(e: AppEvent) {
  switch (e.type) {
    case "download":
      e; // Type is DownloadEvent 
      break;
    case "upload":
      e; // Type is UploadEvent 
      break;
  }
}

这种模式也被称为 ”标签联合“ 或 ”可辨识联合“,它在 TypeScript 中的应用范围非常广。

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

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

相关文章

Linux 线程同步——信号量、互斥锁、读写锁

一、线程同步的概念 这里的同步就是对程序的执行进行控制,因为如果不进行控制就会出现错误的问题,这里的控制是为了保证程序的正确性。 线程同步指的是当一个线程在对某个临界资源进行操作时,其他线程都不可以对这个资源进行操作&#xff0…

自然语言处理: 第九章DeepSpeed的实践

理论基础 仓库链接: microsoft/DeepSpeed: DeepSpeed is a deep learning optimization library that makes distributed training and inference easy, efficient, and effective. DeepSpees正如它官网介绍的一样,它为深度学习模型提供了一站式的快速以及大规模…

【SA8295P 源码分析】03 - SA8295P QNX Host上电开机流程分析

【SA8295P 源码分析】03 - SA8295P QNX Host上电开机流程分析 一、阶段1 固件开机自检 (SM BIST):APPS PBL加载XBL后触发 INT_RESET进行Warm Reset二、阶段2 固件开机自检 (SM BIST):加载TZ,初始Hypervisor,启动QNX Kernel&#x…

k8编写yaml文件小工具

在刚接触k8s的时候觉得yaml资源文件非常的难写,完全看不懂,经过一段时间的摸索学习,发现k8s平台中是提供了一系列的工具和技巧的,可以帮助我们很好的编写资源文件,提升编写yaml文件的能力,常用的命令工具是…

python爬虫9:实战2

python爬虫9:实战2 前言 ​ python实现网络爬虫非常简单,只需要掌握一定的基础知识和一定的库使用技巧即可。本系列目标旨在梳理相关知识点,方便以后复习。 申明 ​ 本系列所涉及的代码仅用于个人研究与讨论,并不会对网站产生不好…

时序预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络时间序列预测

时序预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络时间序列预测 目录 时序预测 | MATLAB实现SO-CNN-LSTM蛇群算法优化卷积长短期记忆神经网络时间序列预测预测效果基本介绍程序设计学习总结参考资料 预测效果 基本介绍 时序预测 | MATLAB实现SO-CNN-LSTM蛇群…

深入解析淘宝API,实现高效商务应用

淘宝API的基本调用 1. API文档与SDK 淘宝API官方提供了详细的API文档,包含了API的使用说明、参数列表、示例代码等内容。开发者可以通过文档了解每个API接口的具体功能和使用方法。此外,淘宝API还提供了多种编程语言的SDK,方便开发者进行快速…

桌游新篇:3.1 UserCase分析

距离上一次停止更新这个系列有将近9个月了。 工作这么久,学会了一件事,就是想清楚再动手。当然,后续工作已经渐渐展开了,而且当下属于天时地利人和(既有当前MR设备带来的硬件buff,又有大语言模型&#xff…

SOPC之NIOS Ⅱ实现电机转速PID控制

通过FPGA开发板上的NIOS Ⅱ搭建电机控制的硬件平台,包括电机正反转、编码器的读取,再通过软件部分实现PID算法对电机速度进行控制,使其能够渐近设定的编码器目标值。 一、PID算法 PID算法(Proportional-Integral-Derivative Algo…

21-注意点说明:scoped样式冲突 / data

组件的三大组成部分 - 注意点说明 组件的样式冲突 scoped 默认情况:写在组件中的样式会 全局生效 -> 因此很容易造成多个组件之间的样式冲突问题 1.全局样式: 默认组件中的样式会作用到全局 2.局部样式: 可以给组件加上 scoped 属性,可以让样式只作用于当前组件 scoped原理…

《有效调节情绪,保持工作心态平和》

工作中,我们有时会遇到各种挑战和困难,这些挑战和困难可能引发我们的负面情绪,例如焦虑、愤怒和沮丧等。然而,保持稳定的情绪是实现高效工作的重要因素之一。本文将分享如何在工作中保持稳定的情绪。 首先,让我们来谈谈…

Spring Boot 如何通过jdbc+HikariDataSource 完成对Mysql 操作

😀前言 本篇博文是关于Spring Boot 如何通过jdbcHikariDataSource 完成对Mysql 操作的说明,希望你能够喜欢😊 🏠个人主页:晨犀主页 🧑个人简介:大家好,我是晨犀,希望我的…

Python多组数据三维绘图系统

文章目录 增添和删除坐标数据更改绘图逻辑源代码 Python绘图系统: 基础:将matplotlib嵌入到tkinter 📈简单的绘图系统 📈数据导入📈三维绘图系统自定义控件:坐标设置控件📉坐标列表控件 增添和…

录屏有哪些讲究?有哪些好用的录屏软件?

在如今数字时代,视频分享已经成为一种流行的传播方式。为了制作高质量的视频内容,录屏已经成为了一种必备的技能。但是,要想制作出令人满意的录屏视频,需要了解一些讲究和使用一些好用的录屏软件。 录屏是一种视觉传达方式&#x…

【prism】发布订阅和取消订阅,进一步梳理

一个对象对应一个事件订阅 一个事件是可以被重复订阅的,如果一个事件被订阅了三次,那边发布一次该事件,就会触发三次事件订阅: 通过观察Prism的事件聚合器对象,发现它此时包含了三个事件对象,其中第三个事件订阅数量达到了3! 这样的话,如果调用一次 Publish ,那么S…

Android 获取 SHA256 签名

在 Android Studio 中的 Terminal ,输入命令: keytool -list -v -keystore debug.keystore 如果出现以下提示: keytool -genkey -v -keystore debug.keystore -alias androiddebugkey -keyalg RSA -validity 10000 按照提示输入相关信息,…

SIP 7英寸触摸屏寻呼主机

SV-8006TP SIP7英寸触摸屏寻呼主机 一、描述 SV-8006TP是我司的一款SIP桌面式对讲广播主机,具有10/100M以太网接口,从网络接口接收网络的音频数据,提供立体声音频输出。 SV-8006TP寻呼话筒可以通过麦克风或者本地线路输入对终端进行分区广…

Java【手撕双指针】LeetCode 283. “移动零“, 图文详解思路分析 + 代码

文章目录 前言一、移动零1, 题目2, 思路分析3, 代码展示 前言 各位读者好, 我是小陈, 这是我的个人主页, 希望我的专栏能够帮助到你: 📕 JavaSE基础: 基础语法, 类和对象, 封装继承多态, 接口, 综合小练习图书管理系统等 📗 Java数据结构: 顺序表, 链表,…

传统图像处理之直方图均衡化

重要说明:本文从网上资料整理而来,仅记录博主学习相关知识点的过程,侵删。 一、参考资料 直方图均衡化的原理及实现 图像处理之直方图均衡化 二、直方图 1. 直方图的概念 图像的灰度直方图,描述了图像中灰度分布情况&#xf…

BaiChuan13B多轮对话微调范例

前方干货预警:这可能是你能够找到的,最容易理解,最容易跑通的,适用于多轮对话数据集的大模型高效微调范例。 我们构造了一个修改大模型自我认知的3轮对话的玩具数据集,使用QLoRA算法,只需要5分钟的训练时间…