Typescript高级: 详解 any、object、Object、Record<any, any> 和泛型 T

2024-05-13 02:04

本文主要是介绍Typescript高级: 详解 any、object、Object、Record<any, any> 和泛型 T,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

any 类型

  • any 类型是 TypeScript 中一个特殊的类型,它表示可以是任意类型的值

  • 使用 any 类型会失去 TypeScript 的类型检查能力

  • 相当于告诉编译器“相信我,我知道这个值的类型”

  • 语法示例

    let anyVar: any;
    anyVar = 123;
    anyVar = "hello";
    anyVar = { name: "Alice" };// 使用 any 类型后,再用 is 来指定某一类型
    if (typeof anyVar === "string") {console.log(anyVar.toUpperCase()); // 编译器不会报错
    }
    
  • 在上面的例子中,anyVar 可以被赋值为任何类型的值

  • 在 if 语句中,我们使用了 JavaScript 的 typeof 操作符来检查 anyVar 是否为字符串类型

  • 如果是,则调用 toUpperCase() 方法,此时虽然使用了 any 类型

  • 但通过 is 类型的检查,我们仍然可以安全地使用字符串的方法

  • any 类型它允许你在编译时绕过类型检查

  • 换句话说,当你将一个变量声明为 any 类型时,你可以为它赋予任何类型的值

  • 并且TypeScript编译器不会对该变量进行类型检查

  • 这在你刚开始使用TypeScript或不确定某个变量的确切类型时非常有用

  • 但过度依赖 any 可能会使你的代码失去类型安全性,从而引入运行时错误

  • 应用any,在编译期可能会通过,但在运行时,可能会报错

    let value: any;value = "Hello, world!"; // 可以是字符串
    value = 42;             // 可以是数字
    value = true;           // 可以是布尔值
    value = { name: "Alice" }; // 可以是对象
    value = [1, 2, 3];      // 可以是数组
    value = null;           // 可以是null
    value = undefined;      // 可以是undefined// 调用任何方法或访问任何属性都不会引起编译错误
    value.someNonExistentMethod(); // 运行时可能会出错,因为方法可能不存在
    console.log(value.someNonExistentProperty); // 运行时可能是undefined,因为属性可能不存在
    
  • 注意事项

    • 虽然 any 类型非常灵活,但过度使用它会带来一些负面影响:
      • 失去类型安全性:使用 any 意味着你放弃了TypeScript提供的类型检查,这可能导致运行时错误,因为编译器不会为你捕获类型不匹配的问题
      • 可读性和可维护性降低:当代码中的变量或函数返回类型为 any 时,其他开发者可能不清楚这个变量的确切类型,从而增加了理解和维护的难度
      • 丧失类型推断:当你使用 any 类型时,TypeScript的类型推断功能将不再适用,因为编译器不会尝试推断 any 变量的更具体类型
  • 最佳实践

    • 避免使用 any:尽量使用具体的类型,而不是 any。这有助于捕获类型错误并增强代码的可读性和可维护性
    • 逐步替换 any:如果你正在处理一个大型代码库,并且其中已经使用了大量的 any,考虑逐步替换它们为更具体的类型。这可能需要一些时间和努力,但可以提高代码的质量
    • 使用类型守卫和类型断言:如果你确实需要处理不确定类型的值,可以考虑使用类型守卫(通过条件语句或类型谓词函数)来缩小类型的范围,或使用类型断言(as 语法)来明确告诉编译器你期望的类型
  • 使用类型守卫和类型断言示例

    function isNumber(value: any): value is number {return typeof value === 'number';
    }function processValue(value: any) {if (isNumber(value)) {// 在这个分支中,TypeScript知道value是number类型console.log(value * 2);} else {// 否则,value仍然是any类型console.log(value.toString()); // 这里可能需要进一步的类型检查或断言}
    }let someValue: any = 42;
    processValue(someValue); // 输出:84someValue = "Hello";
    processValue(someValue); // 输出:Hello// 使用类型断言
    let definitelyNumber: any = "This is not a number";
    let numberValue = definitelyNumber as number; // 这里不会有编译错误,但运行时可能会有问题
    console.log(numberValue * 2); // NaN 因为definitelyNumber实际上不是numberfunction isString(value: any): value is string {return typeof value === "string";
    }function showValue(value: any) {if (isString(value)) {// 在这里,TypeScript 知道 value 是 string 类型console.log("Value is a string:", value.toUpperCase());} else {// 在这里,value 仍然是 any 类型,因为我们没有更多的信息来确定它的类型console.log("Value is not a string:", value);}
    }// 使用示例
    showValue("Hello"); // 输出: Value is a string: HELLO
    showValue(42); // 输出: Value is not a string: 42
    
  • 在使用类型断言时,你需要格外小心

  • 确保你确实知道变量在运行时的确切类型

  • 否则你可能会引入难以调试的运行时错误

object 类型

  • object 类型表示非原始类型,即不是 null、undefined、number、string、boolean 或 symbol 的类型
  • 当你想要表示一个变量可以是任何对象类型(但不包括原始类型)时,你可以使用 object 类型,这包括所有的对象,数组,函数等

1 )简单示例

let objVar: object;
objVar = {}; // 正确
objVar = []; // 正确
objVar = function() {}; // 正确
objVar = 123; // 编译错误: 不能将类型“number”分配给类型“object”。,因为 123 是 number 类型,不是 object

2 )使用对象字面量

let person: object = { name: "Bob", age: 25 }; // person 的类型是 { name: string; age: number; }

3 ) 使用索引签名

如果你想要一个对象,其属性可以是任何类型
但你知道这些属性都有一个共同的键类型,你可以使用索引签名

let dict: { [key: string]: string | number } = {}; // 任意字符串键,值类型为字符串或数字
dict["key1"] = "value1"; // 正确
dict["key2"] = 42; // 正确
// dict["key3"] = true; // 错误:不能将类型 'boolean' 分配给类型 'string | number'
console.log(dict) // { key1: 'value1', key2: 42 }

4 ) 与其他类型结合使用

  • 你也可以将 object 类型与其他类型结合使用,创建更复杂的类型
    type User = {id: number;name: string;
    } & object; // 确保 User 是 object 类型的一个子集,并且具有特定的属性const user: User = { id: 1, name: "Charlie" }; // 正确
    console.log(user)
    

5 ) 类型守卫与 object 类型

  • 虽然 object 类型本身不需要类型守卫,但如果你在处理联合类型

  • 并且想要区分一个值是否是对象,你可以使用类型守卫

    function isObject(item: any): item is object {return item !== null && typeof item === 'object';
    }function processValue(value: string | number | object) {if (isObject(value)) {// 在这里,TypeScript 知道 value 是 object 类型console.log(Object.keys(value));} else {// value 是 string 或 number 类型console.log(typeof value);}
    }processValue({ a: 1 }); // 输出对象的键
    processValue("hello"); // 输出 "string"
    
  • 注意:

    • 当使用 object 类型时,你应该确保你确实需要这个类型
    • 而不是更具体的类型,因为使用 object 类型会失去类型安全性
    • object 类型不包括数组,因为数组在 TypeScript 中是特殊的对象
    • 具有额外的属性和方法(如 length 和数组方法)
    • 如果你想要一个类型包括数组和所有其他对象
    • 你应该使用更宽松的类型,如 any 或创建一个联合类型,如 any[] | object
    • 当你想要确保一个对象具有某些特定的属性时
    • 最好使用接口或类型别名来定义这些属性,而不是简单地使用 object 类型
    • 这样可以提供更好的类型检查和代码可读性

Object 类型

  • 在 TypeScript 中,Object 实际上是指向 JavaScript 的全局 Object 构造函数的类型
  • 它是所有类型的超类型,包括原始类型如 string, number, boolean 等
  • 然而,直接使用 Object 类型通常不是一个好的实践,因为它会失去太多的类型信息
  • 是 JavaScript 的全局对象类型,不常用作 TypeScript 中的类型注解
  • 基本使用
    let obj: Object;obj = {}; // 空对象字面量
    obj = { name: "Alice", age: 30 }; // 对象字面量
    obj = [1, 2, 3]; // 数组(也是对象)
    obj = function() { console.log("Hello"); }; // 函数(也是对象)obj = 42; // 正确
    obj = "Hello"; // 正确
    obj = Symbol("1") // 正确
    // obj = undefined // 编译错误 Type 'undefined' is not assignable to type 'Object'.
    // obj = null // 编译错误 Type 'null' is not assignable to type 'Object'.
    console.log(obj)// 如果你确实需要一个可以包含任意键值对的对象
    // 你可以使用索引签名来定义一个更灵活的类型
    interface FlexibleObject {[key: string]: any;
    }let flexible: FlexibleObject = { name: "Charlie", age: 35, address: "123 Street" }; // 正确
    console.log(flexible) // { name: 'Charlie', age: 35, address: '123 Street' }
    

泛型 T

  • 泛型(Generics)是编程语言提供的一种特性
  • 它允许在定义函数、接口或类的时候,不预先指定具体的类型
  • 而在使用的时候再指定类型的一种特性
  • 在 TypeScript 中,泛型非常常用,它可以帮助我们编写可重用的组件
  • 这些组件可以支持多种类型的数据
  • T 是 TypeScript 中定义泛型时常用的一个类型变量
  • 它表示“Type”,即类型

1 ) 给泛型设定一个默认的类型

type DefaultToAny<T = any> = T extends unknown ? T : any;function withDefaultAny<T = DefaultToAny>(value: T): T {return value;
}// 使用示例
const str = withDefaultAny("hello"); // T 被推断为 string
const num = withDefaultAny(42); // T 被推断为 number
const anyValue = withDefaultAny({}); // T 默认为 anyconsole.log(str) // hello
console.log(num) // 42
console.log(anyValue) // {}
  • 在上面的代码中,DefaultToAny 类型是一个条件类型
  • 它接受一个泛型参数 T,并默认该参数为 any
  • withDefaultAny 函数使用了这个类型作为默认类型参数
  • 从而实现了当调用者没有提供泛型参数时,T 默认为 any 的效果

2 ) 一般使用

function firstElement<T>(arr: T[]): T | undefined {return arr.length > 0 ? arr[0] : undefined;
}// 使用字符串数组
const strArr = ['a', 'b', 'c'];
const firstStr = firstElement(strArr); // T 被推断为 string,firstStr 的类型是 string | undefined// 使用数字数组
const numArr = [1, 2, 3];
const firstNum = firstElement(numArr); // T 被推断为 number,firstNum 的类型是 number | undefined// 也可以显式指定泛型参数
const explicitStr = firstElement<string>(['x', 'y']); // 显式指定 T 为 string
  • 在这个例子中,firstElement 函数使用了泛型 T 来表示数组元素的类型
  • 当我们传入一个字符串数组时,TypeScript 会自动推断出 T 是 string 类型
  • 同样地,如果传入一个数字数组,T 会被推断为 number 类型
  • 如果我们希望显式地指定泛型参数,也可以在调用函数时这样做
  • 泛型极大地增强了 TypeScript 的类型安全性和代码的复用性
  • 允许我们编写更加灵活和通用的代码
  • 泛型的好处
    • 编译期对类上调用方法或属性时的泛型类型进行安全检查(类型安全检查),不符合泛型实际参数类型(泛型实参类型) 就编译通不过,防止不符合条件的数据增加进来
    • 一种泛型类型被具体化成某种数据类型后,该数据类型的变量获取属性和方法时会有自动提示,这无疑提高代码开发效率和减少出错率
  • 泛型的特点
    • 定义时不明确使用时必须明确成某种具体数据类型的数据类型,这里体现出了泛型的宽泛
    • 编译期间进行数据类型安全检查的数据类型,这体现了泛型的严谨
    • 特别注意:
      • 1 ) 类型安全检查发生在编译期间
      • 2 )泛型是参数化的数据类型, 使用时明确化后的数据类型就是参数的值
  • 泛型类的格式: class 类名<泛型形参类型>
    • 泛型形参类型一般有两种表示
    • 1 )A-Z 任何一个字母
    • 2 )语义化的单词来表示
    • 绝大多数情况,泛型都是采用第一种形式表示,如下:
       class ArrayList<T> {  array: Array<T>add(data:T) {...}...}
      

Record<any, any> 类型

  • Record<K, T> 是 TypeScript 中的一个实用的内置工具类型,用于构造一个对象类型,其中对象的键类型为 K,值的类型为 T
  • 如果 K 是一个联合类型(union type),那么生成的对象的键可以是联合类型中列出的任何类型
  • 当你使用 Record<any, any>,你实际上是在创建一个可以接受任何键和任何值的对象类型
  • Record<any, any> 表示一个对象的键和值都可以是任意类型
  • 这与 { [key: string]: any } 索引签名非常相似,但 Record<any, any> 更加明确,并且利用了 TypeScript 的类型系统

1 )简单示例

let recordVar: Record<any, any>;
recordVar = { name: "Alice", age: 30 }; // 正确
recordVar = { pi: 3.14, sayHello: () => {} }; // 正确
recordVar = 123; // 错误,因为 123 不是对象

2 )复杂示例

// 定义一个类型,使用 Record<any, any>
type LooseObject = Record<any, any>;// 创建一个 LooseObject 类型的变量
const obj: LooseObject = {aString: "Hello",aNumber: 42,aBoolean: true,anObject: { nested: "value" },anArray: [1, 2, 3],aSymbol: Symbol("unique"),// 可以添加任意类型的属性和值
};// 访问属性(TypeScript 不会检查属性是否存在或类型是否正确)
console.log(obj.aString); // 输出: Hello
console.log(obj.aNumber); // 输出: 42// 添加新属性(同样,TypeScript 不会检查类型)
obj.newProperty = "newValue";// 试图访问一个不存在的属性,TypeScript 编译时不会报错,但运行时可能会是 undefined
console.log(obj.nonExistentProperty); // 输出: undefined
  • 注意
    • Record<any, any> 是一个非常宽松的类型
    • 它允许你在对象中添加任何键和任何值
    • 这类似于 JavaScript 中的普通对象,但在 TypeScript 中使用它会失去类型检查的好处
    • 由于 any 类型是 TypeScript 中的顶级类型,它可以被赋值给任何其他类型,反之亦然
    • 因此,使用 Record<any, any> 时,TypeScript 编译器不会对对象的属性进行类型检查
    • 尽管 Record<any, any> 在某些情况下可能很有用(例如,当你需要处理来自外部源的数据,且该数据的结构未知时),但在大多数情况下,最好避免使用它,以保持类型安全和代码的可维护性
    • 如果你知道对象的键和值的类型,最好显式地定义它们,而不是使用 Record<any, any>
    • 这样,TypeScript 可以为你提供更强的类型检查和更好的开发体验
    • 在实际开发中,应该尽可能地使用具体的类型而不是 any,以利用 TypeScript 提供的类型安全优势
    • 如果确实需要处理未知结构的数据,可以考虑使用类型守卫(type guards)或其他方式来逐步缩小类型的范围,以增加代码的健壮性

总结

1 )object 为什么不能替代类上的泛型

  • 1.1 编译期间 object 无法进行类型安全检查,而泛型在编译期间可以进行类型安全检查
    • object 接受也只能接受所有的 object 类型的变量
    • 比如有 Customer、Student、Dog 类的实例都是对象类型
    • 或者自己定义的对象,都可以传递给 object 类型的方法参数或属性
    • 但如果我们只希望添加Customer类的对象
    • 当添加其他类的对象必须出现编译错误
    • 但是 object 无法做到,就只能用泛型了
  • 1.2 object 类型数据无法接受非 object 类型的变量,只能接受 object 类型的变量,泛型能轻松做到
    • 正因为 object 接受也只能接受所有的 object 类型的变量
    • 那么如果有一个集合类[数组封装类]有一个 add 方法
    • 允许每次添加指定类型的变量到 add 方法的参数
    • 比如
      • 我们第一轮的希望添加 10 次字符串类型的变量
      • 第二轮的希望添加 10 次整数类型变量
      • 第三轮的希望添加 10 次顾客类型的变量,泛型轻松做到
    • object 类型数据无法接受任意非 object 类型的变量,object 只能接受所有的 object 类型的变量
  • 1.3 object 类型数据获取属性和方法时无自动提示,泛型有自动提示
    • 一种泛型类型被具体化成某种数据类型后,该数据类型的变量获取属性和方法时会有自动提示,提高代码开发效率和减少出错率
    • 但在 object 类型的变量无法获取数据类型的属性和方法,降低了体验感和开发效率

2 ) any 为什么不能替代类上的泛型

  • 编译期间 any 无法进行类型安全检查,而泛型在编译期间可以进行类型安全检查
    • any 是所有类型的父类,也是所有类型的子类
    • 如果我们现在是一个宠物店类
      • 希望只能添加 Dog 类
      • 当调用 add 方法添加 Customer、Student 类必定出现编译错误
      • 从而保证了类型安全检查
    • 但是 any 类型无法保证类型安全检查,可以为任意类型
      • 包括 string,number,boolean,null,undefined,never,void,unknown 基础数据类型
      • 和数组,类,接口类型, type 类型的变量全部能接受
      • 不会进行无法进行类型安全检查
  • any 类型可以获取任意数据类型的任何属性和任意方法而不会出现编译错误导致潜在错误风险,而泛型却有效的避免了此类问题发生
    • any 类型可以获取任何属性和任意方法而不会出现编译错误,因为any可以代表任意数据类型来获取任意属性和任意方法
    • 但是泛型类型被具体化成某种数据类型后,该数据类型的变量调用该数据类型之外的属性和方法时,出现编译错误,这也减少了代码隐藏潜在错误的风险
  • any 类型数据获取属性和方法时无自动提示,泛型有自动提示
  • any 类型可以代表任意数据类型来获取任何属性和任意方法而不会出现编译错误
    • 因为any可以代表任意数据类型来获取任意属性和任意方法
    • any 的这个特性是一把双刃剑,当我们需要这么使用
    • 它给我们带来方便,但是大多数情况下我们是不需要这么做的

这篇关于Typescript高级: 详解 any、object、Object、Record<any, any> 和泛型 T的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/984375

相关文章

Mysql 中的多表连接和连接类型详解

《Mysql中的多表连接和连接类型详解》这篇文章详细介绍了MySQL中的多表连接及其各种类型,包括内连接、左连接、右连接、全外连接、自连接和交叉连接,通过这些连接方式,可以将分散在不同表中的相关数据... 目录什么是多表连接?1. 内连接(INNER JOIN)2. 左连接(LEFT JOIN 或 LEFT

Java中switch-case结构的使用方法举例详解

《Java中switch-case结构的使用方法举例详解》:本文主要介绍Java中switch-case结构使用的相关资料,switch-case结构是Java中处理多个分支条件的一种有效方式,它... 目录前言一、switch-case结构的基本语法二、使用示例三、注意事项四、总结前言对于Java初学者

Linux内核之内核裁剪详解

《Linux内核之内核裁剪详解》Linux内核裁剪是通过移除不必要的功能和模块,调整配置参数来优化内核,以满足特定需求,裁剪的方法包括使用配置选项、模块化设计和优化配置参数,图形裁剪工具如makeme... 目录简介一、 裁剪的原因二、裁剪的方法三、图形裁剪工具四、操作说明五、make menuconfig

详解Java中的敏感信息处理

《详解Java中的敏感信息处理》平时开发中常常会遇到像用户的手机号、姓名、身份证等敏感信息需要处理,这篇文章主要为大家整理了一些常用的方法,希望对大家有所帮助... 目录前后端传输AES 对称加密RSA 非对称加密混合加密数据库加密MD5 + Salt/SHA + SaltAES 加密平时开发中遇到像用户的

Springboot使用RabbitMQ实现关闭超时订单(示例详解)

《Springboot使用RabbitMQ实现关闭超时订单(示例详解)》介绍了如何在SpringBoot项目中使用RabbitMQ实现订单的延时处理和超时关闭,通过配置RabbitMQ的交换机、队列和... 目录1.maven中引入rabbitmq的依赖:2.application.yml中进行rabbit

C语言线程池的常见实现方式详解

《C语言线程池的常见实现方式详解》本文介绍了如何使用C语言实现一个基本的线程池,线程池的实现包括工作线程、任务队列、任务调度、线程池的初始化、任务添加、销毁等步骤,感兴趣的朋友跟随小编一起看看吧... 目录1. 线程池的基本结构2. 线程池的实现步骤3. 线程池的核心数据结构4. 线程池的详细实现4.1 初

Python绘制土地利用和土地覆盖类型图示例详解

《Python绘制土地利用和土地覆盖类型图示例详解》本文介绍了如何使用Python绘制土地利用和土地覆盖类型图,并提供了详细的代码示例,通过安装所需的库,准备地理数据,使用geopandas和matp... 目录一、所需库的安装二、数据准备三、绘制土地利用和土地覆盖类型图四、代码解释五、其他可视化形式1.

SpringBoot使用Apache POI库读取Excel文件的操作详解

《SpringBoot使用ApachePOI库读取Excel文件的操作详解》在日常开发中,我们经常需要处理Excel文件中的数据,无论是从数据库导入数据、处理数据报表,还是批量生成数据,都可能会遇到... 目录项目背景依赖导入读取Excel模板的实现代码实现代码解析ExcelDemoInfoDTO 数据传输

如何用Java结合经纬度位置计算目标点的日出日落时间详解

《如何用Java结合经纬度位置计算目标点的日出日落时间详解》这篇文章主详细讲解了如何基于目标点的经纬度计算日出日落时间,提供了在线API和Java库两种计算方法,并通过实际案例展示了其应用,需要的朋友... 目录前言一、应用示例1、天安门升旗时间2、湖南省日出日落信息二、Java日出日落计算1、在线API2

使用Spring Cache时设置缓存键的注意事项详解

《使用SpringCache时设置缓存键的注意事项详解》在现代的Web应用中,缓存是提高系统性能和响应速度的重要手段之一,Spring框架提供了强大的缓存支持,通过​​@Cacheable​​、​​... 目录引言1. 缓存键的基本概念2. 默认缓存键生成器3. 自定义缓存键3.1 使用​​@Cacheab