浅谈typescript中keyof与typeof操作符用法

目录
  • 一、keyof 简介
  • 二、keyof 的作用
  • 三、keyof 与对象的数值属性
  • 四、keyof 与 typeof 操作符

一、keyof 简介

TypeScript 允许我们遍历某种类型的属性,并通过 keyof 操作符提取其属性的名称。keyof 操作符是在 TypeScript 2.1 版本引入的,该操作符可以用于获取某种类型的所有键,其返回类型是联合类型。
下面我们来看个例子:

interface Person {
  name: string;
  age: number;
  location: string;
}

type K1 = keyof Person; // "name" | "age" | "location"
type K2 = keyof Person[];  // number | "length" | "push" | "concat" | ...
type K3 = keyof { [x: string]: Person };  // string | number

除了接口外,keyof 也可以用于操作类,比如:

class Person {
  name: string = "Semlinker";
}

let sname: keyof Person;
sname = "name";

若把 sname = “name” 改为 sname = “age” 的话,TypeScript 编译器会提示以下错误信息:

Type '"age"' is not assignable to type '"name"'.

keyof 操作符除了支持接口和类之外,它也支持基本数据类型:

let K1: keyof boolean; // let K1: "valueOf"
let K2: keyof number; // let K2: "toString" | "toFixed" | "toExponential" | ...
let K3: keyof symbol; // let K1: "valueOf"

此外 keyof 也称为输入索引类型查询,与之相对应的是索引访问类型,也称为查找类型。在语法上,它们看起来像属性或元素访问,但最终会被转换为类型:

type P1 = Person["name"];  // string
type P2 = Person["name" | "age"];  // string | number
type P3 = string["charAt"];  // (pos: number) => string
type P4 = string[]["push"];  // (...items: string[]) => number
type P5 = string[][0];  // string

二、keyof 的作用

JavaScript 是一种高度动态的语言。有时在静态类型系统中捕获某些操作的语义可能会很棘手。以一个简单的prop 函数为例:

function prop(obj, key) {
  return obj[key];
}

该函数接收 obj 和 key 两个参数,并返回对应属性的值。对象上的不同属性,可以具有完全不同的类型,我们甚至不知道 obj 对象长什么样。

那么在 TypeScript 中如何定义上面的 prop 函数呢?我们来尝试一下:

function prop(obj: object, key: string) {
  return obj[key];
}

在上面代码中,为了避免调用 prop 函数时传入错误的参数类型,我们为 obj 和 key 参数设置了类型,分别为 {} 和 string 类型。然而,事情并没有那么简单。针对上述的代码,TypeScript 编译器会输出以下错误信息:

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{}'

元素隐式地拥有 any 类型,因为 string 类型不能被用于索引 {} 类型。要解决这个问题,你可以使用以下非常暴力的方案:

function prop(obj: object, key: string) {
  return (obj as any)[key];
}

很明显该方案并不是一个好的方案,我们来回顾一下 prop 函数的作用,该函数用于获取某个对象中指定属性的属性值。因此我们期望用户输入的属性是对象上已存在的属性,那么如何限制属性名的范围呢?这时我们可以利用本文的主角 keyof 操作符:

function prop<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

在以上代码中,我们使用了 TypeScript 的泛型和泛型约束。首先定义了 T 类型并使用 extends 关键字约束该类型必须是 object 类型的子类型,然后使用 keyof 操作符获取 T 类型的所有键,其返回类型是联合类型,最后利用 extends 关键字约束 K 类型必须为 keyof T 联合类型的子类型。 是骡子是马拉出来遛遛就知道了,我们来实际测试一下:

type Todo = {
  id: number;
  text: string;
  done: boolean;
}

const todo: Todo = {
  id: 1,
  text: "Learn TypeScript keyof",
  done: false
}

function prop<T extends object, K extends keyof T>(obj: T, key: K) {
  return obj[key];
}

const id = prop(todo, "id"); // const id: number
const text = prop(todo, "text"); // const text: string
const done = prop(todo, "done"); // const done: boolean

很明显使用泛型,重新定义后的 prop<T extends object, K extends keyof T>(obj: T, key: K) 函数,已经可以正确地推导出指定键对应的类型。那么当访问 todo 对象上不存在的属性时,会出现什么情况?比如:

const date = prop(todo, "date");

对于上述代码,TypeScript 编译器会提示以下错误:

Argument of type '"date"' is not assignable to parameter of type '"id" | "text" | "done"'.

这就阻止我们尝试读取不存在的属性。

三、keyof 与对象的数值属性

在使用对象的数值属性时,我们也可以使用 keyof 关键字。请记住,如果我们定义一个带有数值属性的对象,那么我们既需要定义该属性,又需要使用数组语法访问该属性, 如下所示:

class ClassWithNumericProperty {
  [1]: string = "Semlinker";
}

let classWithNumeric = new ClassWithNumericProperty();
console.log(`${classWithNumeric[1]} `);

下面我们来举个示例,介绍一下在含有数值属性的对象中,如何使用 keyof 操作符来安全地访问对象的属性:

enum Currency {
  CNY = 6,
  EUR = 8,
  USD = 10
}

const CurrencyName = {
  [Currency.CNY]: "人民币",
  [Currency.EUR]: "欧元",
  [Currency.USD]: "美元"
};

console.log(`CurrencyName[Currency.CNY] = ${CurrencyName[Currency.CNY]}`);
console.log(`CurrencyName[36] = ${CurrencyName[6]}`);

上面的代码中,首先定义了一个 Currency 枚举用于表示三种货币类型,接着定义一个 CurrencyName 对象,该对象使用数值属性作为键,对应的值是该货币类型的名称。该代码成功运行后,控制台会输出以下结果:

CurrencyName[Currency.CNY] = 人民币
CurrencyName[36] = 人民币

为了方便用户能根据货币类型来获取对应的货币名称,我们来定义一个 getCurrencyName 函数,具体实现如下:

function getCurrencyName<T, K extends keyof T>(key: K, map: T): T[K] {
  return map[key];
}

console.log(`name = ${getCurrencyName(Currency.CNY, CurrencyName)}`);

同样,getCurrencyName 函数和前面介绍的 prop 函数一样,使用了泛型和泛型约束,从而来保证属性的安全访问。最后,我们来简单介绍一下 keyof 与 typeof 操作符如何配合使用。

四、keyof 与 typeof 操作符

typeof 操作符用于获取变量的类型。因此这个操作符的后面接的始终是一个变量,且需要运用到类型定义当中。为了方便大家理解,我们来举一个具体的示例:

type Person = {
  name: string;
  age: number;
}

let man: Person = {
  name: "Semlinker",
  age: 30
}

type Human = typeof man;

了解完 typeof 和 keyof 操作符的作用,我们来举个例子,介绍一下它们如何结合在一起使用:

const COLORS = {
  red: 'red',
  blue: 'blue'
}

// 首先通过typeof操作符获取color变量的类型,然后通过keyof操作符获取该类型的所有键,
// 即字符串字面量联合类型 'red' | 'blue'

type Colors = keyof typeof COLORS 
let color: Colors;
color = 'red' // Ok
color = 'blue' // Ok

// Type '"yellow"' is not assignable to type '"red" | "blue"'.
color = 'yellow' // Error

最后留到思考题,有兴趣的小伙伴可以想一想:

interface StringIndexArray {
  [index: string]: string;
}

interface NumberIndexArray {
  [index: number]: string;
}

type K1 = keyof StringIndexArray // type K1 = string | number
type K2 = keyof NumberIndexArray // type K2 = number

到此这篇关于浅谈typescript中keyof与typeof操作符用法的文章就介绍到这了,更多相关typescript中keyof与typeof操作符内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

时间: 2022-06-22

TypeScript 中接口详解

在 TypeScript 中,接口是用作约束作用的,在编译成 JavaScript 的时候,所有的接口都会被擦除掉,因为 JavaScript 中并没有接口这一概念. 先看看一个简单的例子: function printLabel(labelledObj: { label: string }) { console.log(labelledObj.label); } var myObj = { size: 10, label: "Size 10 Object" }; printLabel

TypeScript Type Innference(类型判断)

TypeScript 是微软开发的 JavaScript 的超集,TypeScript兼容JavaScript,可以载入JavaScript代码然后运行.TypeScript与JavaScript相比进步的地方 包括:加入注释,让编译器理解所支持的对象和函数,编译器会移除注释,不会增加开销:增加一个完整的类结构,使之更新是传统的面向对象语言. 为什么会有 TypeScript? JavaScript 只是一个脚本语言,并非设计用于开发大型 Web 应用,JavaScript 没有提供类和模块的概

Typescript的三种运行方式(小结)

一.在线complier 这种方式最简单,不需在本地做任何配置安装,只需进入Typescript的官网,点击里面的playground就可以直接写代码了.但这种方式只适用于测试而不适用于开发. 二.本地命令行编译 1.在本地编译运行Typescript需要使用npm下载typescript npm install -g typescript 至于npm,就是node的包管理工具,下载node后就自动带了. 2.下载完成后可以使用 tsc -v 查看版本 3.使用:如在本地创建Hello.ts e

关于TypeScript中import JSON的正确姿势详解

前言 Typescript是微软内部出品的,用actionscript的语法在写js的一门新语言,最近 TypeScript 中毒,想想我一个弱类型出身的人,怎么就喜欢上了类型约束--当然这不是重点,重点可能还是 JS 没有接口,我没法靠 class 语法糖写的非常 OO--下面这篇文章想说的其实是在 ts 中如何正确的 import json 格式. 首先我使用了基本姿势 import * as variable from './fooooooo.json' 结果发现他提示我并没有这个 mod

Typescript 中的 interface 和 type 到底有什么区别详解

interface VS type 大家使用 typescript 总会使用到 interface 和 type,官方规范 稍微说了下两者的区别 An interface can be named in an extends or implements clause, but a type alias for an object type literal cannot. An interface can have multiple merged declarations, but a type

TypeScript学习之强制类型的转换

前言 使用强类型变量常常需要从一种类型向另一种类型转换,通常使用ToString或ParseInt可以来实现一些简单的转换,但是有时候需要像.NET语言中那样将一种类型显示的转换为另一种类型,在TypeScript规范中,被称为"类型断言",它仍然是类型转换,只是语法是有些不同.下面来详细看看TypeScript的强制类型转换. TypeScript强制类型转换 在 TypeScript 中将一个 number 转换成 string ,这样做会报错: var a:number = 12

TypeScript类型声明书写详解

本文总结一下TypeScript类型声明的书写,很多时候写TypeScript不是问题,写类型就特别纠结,我总结下,我在使用TypeScript中遇到的问题.如果你遇到类型声明不会写的时候,多看看lodash的声明,因为lodash对数据进行各种变形操作,所以你能遇到的,都有参考示例. 基本类型 // 变量 const num: number = 1; const str: string = 'str'; const bool: boolean = true; const nulls: null

基于Python的文件类型和字符串详解

1. Python的文件类型 1. 源代码--直接由Python解析 vi 1.py #!/usr/bin/python print 'hello world' 这里的1.py就是源代码 执行方式和shell脚本类似: chmod +x 后,./1.py Python 1.py 2. 字节代码 Python源码文件经编译后生成的扩展名为pyc的文件 编译方法: [root@t1 py]# cat 2.py #!/usr/bin/python import py_compile py_compil

Java枚举类型enum的详解及使用

 Java枚举类型enum的详解及使用 最近跟同事讨论问题的时候,突然同事提到我们为什么Java 中定义的常量值不采用enmu 枚举类型,而采用public final static 类型来定义呢?以前我们都是采用这种方式定义的,很少采用enum 定义,所以也都没有注意过,面对突入起来的问题,还真有点不太清楚为什么有这样的定义.既然不明白就抽时间研究下吧. Java 中的枚举类型采用关键字enum 来定义,从jdk1.5才有的新类型,所有的枚举类型都是继承自Enum 类型.要了解枚举类型,建议大

Spring MVC自定义日期类型转换器实例详解

Spring MVC自定义日期类型转换器实例详解 WEB层采用Spring MVC框架,将查询到的数据传递给APP端或客户端,这没啥,但是坑的是实体类中有日期类型的属性,但是你必须提前格式化好之后返回给它们.说真的,以前真没这样做过,之前都是一口气查询到数据,然后在jsp页面上格式化,最后展示给用户.但是这次不同,这次我纯属操作数据,没有页面.直接从数据库拿数据给它们返数据.它们给我传数据我持久化数据,说到这里一个小问题就默默的来了. 首先把问题还原一下吧(这是一个数据导出功能),下图中用红框圈

初学C++之自定义类型名简化详解

说明:本人使用的是vc++ IDE:vs2013 我在自定义一些类时,有时会取一些很长的名字,但是这不利于使用,这个时候就可以使用类型名简化. class MathAddBBBB { }; using MAB = MathAddBBBB; 除了使用using之外,我们还可以使用typedef来定义: typedef MathAddBBBB mab; mab m(); 以上这篇初学C++之自定义类型名简化详解就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持我们.

java 用泛型参数类型构造数组详解及实例

java 用泛型参数类型构造数组详解及实例 前言: 前一阵子打代码的时候突然想到一个问题.平时我们的数组都是作为一个参数传入方法中的,如果我们要想在方法中创建一个数组怎么样呢?在类型明确的情况下,这是没什么难度的.如果我们传入的参数是泛型类型的参数呢? public static <T> T[] creArray (T obj){ T[] arr = new T[10]; } 像上面这种用T来直接new数组的方法是错误的,会编译时出现一个:Cannot create a generic arr

C# 为String类型增加方法详解

namespace MyExtensionMethods { public static class MyExtensions { public static int MyGetLength(this System.String target) { return target.Length; } } } 使用时,需要引入这个名字空间,引用如下: string str = "dafasdf"; int len = str.MyGetLength(); 以上这篇C# 为String类型增加

Java对象类型的判断详解

instanceof 判断某个对象是否是某个类的实例或者某个类的子类的实例.它的判断方式大概是这样的: public<T> boolean function(Object obj, Class<T> calzz) { if (obj == null) { return false; } try { T t = (T) obj; return true; } catch (ClassCastException e) { return false; } } Class.equals()

MySQL使用TEXT/BLOB类型的知识点详解

一.TEXT和BLOB的区别 TEXT和BLOB家族之间仅有的不同是BLOB类型存储的是二进制数据,没有排序规则或字符集,而TEXT类型有字符集或排序规则.说白了如果要储存中文则选择TEXT. 二.默认值问题 Strict Mode下不能设置默认值,否则会报can't have a default value错: mysql> create table `test`.`text_blob`( -> `a_text` text DEFAULT ' ' , -> `b_blob` blob

MySQL中CHAR和VARCHAR类型演变和详解

一.演变: MySQL数据库的varchar类型在5.0.3以下的版本中的最大长度限制为255,其数据范围可以是0~255. 在MySQL5.0.3及以上的版本中,varchar数据类型的长度支持到了65535,也就是说可以存放65532个字节的数据,起始位和结束位占去了3个字节,也就是说,在5.0.3以下版本中需要使用固定的TEXT或BLOB格式存放的数据可以在高版本中使用可变长的varchar来存放,这样就能有效的减少数据库文件的大小. 如果在varchar中写入大于设定的长度,默认情况下会