TypeScript & 详细解释 in、keyof、extends、索引签名、Record、typeof 的含义(不定时更新)

前言

很早就听过 TypeScript,当时自己试了下觉得挺简单的,就是给数据声明一个类型提高可读和维护性,不过当时 TS 还不盛行,项目也没机会用到就一直落着,随着 TS 不断普及,许多项目都开始引入了 TS,这不最近在 github 看到一些 TS 代码后,心里开始吐槽:
这好好的非要给 JS 装饰的这么花里胡哨的?我都认不出来这是什么语言了,你确定这是提升 JS 语言可读性而不是添乱?有同学跟我一样的请举个手!

后来静下心来想了想,既来之则安之,与其吐槽,不如静下心来好好重温下 TS ,在这期间也提取了一些有必要掌握的关键字,现在再去读一些 TS 代码心里总算有底了,可以说理解这些关键字的含义,对大部分的 TypeScript 代码都能看得懂。

当然了,光看懂还不行,还得学会运用,但我们现在的目标还是先看懂吧~

提示:本文假设你已经认识一些基本的 TS 语法,如果是第一次接触 TS,建议看下我前面写过的 TypeScript 日常基本语法

正文

1. keyof

keyof 可以获取对象里的所有 key ,跟 JS 中的 Object.keys() 类似,假如我们想要获取某个 interface 接口里的所有 key,就可以用到 keyof,我们来看下例子:

interface User {
    
    
	name: string;
	age: number;
	birthday: string;
}

// 下面等同于 type keysType = 'name' | 'age' | 'birthday'
type keysType = keyof User

const key:keysType = 'name'

看起来好像有点鸡助的样子,无非就是复制 key ,别急,它的真正作用在于搭配其它关键字,我们往下看就能知道了。

2. in

2.1 in keyof

in 可以用来配合 keyof,我们从上面得知 keyof 是获取所有的 key,假如我们想对每个 key 进行额外的处理怎么做呢?
这里就可以用到 in 关键字,我们来看下例子:

interface Properties {
    
    
	name: string;
	age: number;
	ate: () => void;
	likeColors: string[];
}

// 将 Properties 的 key: valueType 都复制过来了
type CopyProperties = {
    
    
  [K in keyof Properties]: Properties[K]
}
// 等同于
// type CopyProperties = Properties

由上面例子得知,通过 in keyof 遍历的同时,还能使其它地方能访问到这个 K,有没有开始感受到 keyof 带来的作用了?

2.2 单独用 in

in 单独使用的话跟 JS 中的 in 类似

// 表示 foo 定义的 key 必须包含 a 或 b 或 c
type Foo = {
    
    
	[K in 'a' | 'b' | 'c']: number
}
const obj: Foo = {
    
    
  a: 100,
  b: 200,
  c: 300,
  d: 400  // 报错
}

3. extends

3.1 interface extends

从字面上理解 interface extends 好像就叫接口继承,你可以这么称呼,但我个人更喜欢叫接口合并,我们来看例子。

interface Action {
    
    
	bark: () => void
}
interface People  extends Action{
    
    
	name: string;
	age: number;
}

// 上面的 People 等同于下面 
interface Peoople {
    
    
	name: string;
	age: number;
	bark: () => void
}

3.2 <T extends U>

敲黑板:请忘记之前我们学过的继承概念,因为下来要讲的 extends 根本不叫继承。

从字面上理解 <T extends U > 好像就叫泛型继承?不!这叫泛型约束,什么是泛型约束?这里我总结一下就是:
泛型传递进来的类型必须满足 U 里的所有属性和类型
我们来看例子:

interface U {
    
    
	name: string;
	age: number;
}
type Foo<T extends U> = {
    
    
	colors: string[];
	sex: boolean;
	user: T
}

interface User {
    
    
  name: string;
  age: number;
}
// User 满足 U,所以 TS 不会报错
const firstPerson: Foo<User> = {
    
    
  colors: ['Blue'],
  sex: true,
  user: {
    
    
    name: 'Jack',
    age: 20
  }
}

interface Water {
    
    
	color: 'Transparent';
	age: 10900000000000
}
// Water 这个类型不满足 U,所以 TS 会报错
const secondPerson: Foo<Water> = {
    
    }

通过例子可以得出,定义的泛型被限制住了,不能随便传递,我们再来举另外一个例子来加深印象:

interface Properties {
    
    
	length: number;
}

function countStrLength<T extends Properties>(arg: T) {
    
    
	console.log('字符串的长度是' + arg.length)
}
// 正确,字符串里有 length 属性而且是 number 类型
countStrLength('Hello,world') 
// 传递的 number 类型没有 length 属性, TS 报错!
countStrLength(1000)

注意:泛型约束不仅仅约束 key ,它还限制了 valueType,像上面的 length: number 如果改为 length: string,TS 也会报错,因为 length 返回的是一个 number 类型而不是 string

4. 索引签名

索引签名 指的是 [K : keyType]: valuetype, 其中 K 可以随意命名,说的通俗点就是用来定义未知数量的 keyType: valueType,我们来看下例子就懂了

interface User {
    
    
	name: string;
	age: 20;
}

/*
假设 User 只有 name/age 两个属性,但后续可能会新增其它属性,且数量是未知的,
这种情况下我们就可以用索引签名来代表未知的 keyType: valueType
稍作修改就会变成下面这样
*/

interface User {
    
    
	name: string;
	age: number;
	[k: string]: any
}
// 接下来新增的的属性 TS 都不会报错了。
const firstPeople: User =  {
    
    
	name: 'Jack',
	age: 20,
	sex: 0,
	colors: ['Blue', 'Yellow']
}


// 如果你想让 [k: string]: any 变成可选状态,只需在后面加个问号 `?` 即可

interface User {
    
    
	name: string;
	age: number;
	[k: string]?: any
}
const secondPeople: User = {
    
    
	name: "Tony',
	age: 24,
}

提示:key 的 type 一般只有 string/number/symbol 这三个属性,对我而言 string 已经可以满足大部分需求了,少数情况才会用到 symbol,至于 number 那是少之又少了。

4. Record

Record索引签名很像,都是用于定义未知数量的 keyType: valueType 形式,我们来看下例子

type R = Record<string, {
    
    
	name: string;
	age: number;
}>

const Players: R = {
    
    
	one: {
    
    
		name: 'Jack',
		age: 20
	},
	two: {
    
    
		name: 'Tony',
		age: 21
	},
	// ...
}

4.1 Record 与 索引签名有什么不同?

你觉得哪个能满足你的需求就用哪个。

5. infer

infer 从字面义理解起来比较抽象,但使用起来是比较简单,它就是一个提取类型的作用,
而且要使用它的是有前提条件的,它是必须由extends? 构成组合 ,我们来看下例子:

interface Animal {
    
    
  name: string;
  age: number;
  action: () => number
}

type GetType<T> = T extends {
    
     action: () => infer R} ? R : T

/**
因为 Animal 里的 action 存在,所以提取 action => 里的返回值,并用 R 表示。
以下等同于 type getAnimalType = number
*/
type getAnimalType = GetType<Animal> 

/**
因为不存在 action 所以返回 T
以下等同于 type getAnimalType = boolean
*/
type getAnimalType = GetType<boolean>

infer 不仅可以提取类型,它还支持联合

interface Animal {
    
    
	name: string;
	age: number;
}
type GetType<T> = T extends {
    
     name: infer R; age: infer R; } ? R : T

/**
因为 Animal 里的 name 和 age 都存在,所以提取 name/age 的类型并用 R 表示。
以下等同于 type getAnimalType = string | age
*/
type getAnimalType = GetType<Animal> 

6. typeof

typeof 可以用来复制某个变量里的类型声明
首先我们知道,当声明一个变量时,在没有指定类型的情况下 TS 会自动推导类型,比如

const UserName = 'Jack' 

// TS 自动推导后
const UserName:string = 'Jack' // 如果你有用 vscode 编辑器可以用鼠标移动到这个变量就可以看到效果了

知道这点后,我们再来看看下面的例子就能懂了

const UserName = 'Jack'
const AnotherUser: typeof UserName = 'Tony'

再来举个例子:假如某个函数里有个参数需要复制某个对象里的声明类型,我们就可以这样做:

const dog = {
    
    
	title: 'Dog	can speaking'
	properties: {
    
    
		name: 'Duolo',
		type: 'Big',
		color: 'Yellow',
	},
	actions: {
    
    
		say() {
    
     console.log('Hi, I am a human, not a dog at all.')},
	}
	
}

// 现在这个 properties 默认推导成 properties: any 类型,
function getPropertyByType(properties) {
    
    
	if (feature.name === 'Duolo') {
    
    
		dog.actions.say()
	}
}

// 如果我们要求 properties 传递进来的必须包含 dog.properties 类型,
// 这时就可以用到 typeof 
function getPropertyByType(properties: typeof dog['properties']) {
    
    
	if (feature.name === 'Duolo') {
    
    
		dog.actions.say()
	}
}

学完这几个关键字后,还需要不断去应用加深印象,
否则过不了多久还是会忘记,又得重新回来学习。

有错误欢迎指出,完!

猜你喜欢

转载自blog.csdn.net/cookcyq__/article/details/125087375