07-类型编程好帮手:TypeScript 类型工具(上)
课程
1
开篇:用正确的方式学习 TypeScript
已学完
学习时长: 7分24秒
2
工欲善其事:打造最舒适的 TypeScript 开发环境
学习时长: 19分14秒
3
进入类型的世界:理解原始类型与对象类型
学习时长: 37分40秒
4
掌握字面量类型与枚举,让你的类型再精确一些
学习时长: 24分53秒
5
函数与 Class 中的类型:详解函数重载与面向对象
学习时长: 50分40秒
6
探秘内置类型:any、unknown、never 与类型断言
学习时长: 34分58秒
7
类型编程好帮手:TypeScript 类型工具(上)
学习时长: 33分34秒
8
类型编程好帮手:TypeScript 类型工具(下)
学习时长: 40分52秒
9
类型编程基石:TypeScript 中无处不在的泛型
学习时长: 43分46秒
10
结构化类型系统:类型兼容性判断的幕后
学习时长: 20分48秒
11
类型系统层级:从 Top Type 到 Bottom Type
学习时长: 45分16秒
12
类型里的逻辑运算:条件类型与 infer
学习时长: 52分44秒
13
内置工具类型基础:别再妖魔化工具类型了!
学习时长: 44分47秒
14
反方向类型推导:用好上下文相关类型
学习时长: 18分37秒
15
函数类型:协变与逆变的比较
学习时长: 21分38秒
16
了解类型编程与类型体操的意义,找到平衡点
学习时长: 4分27秒
17
内置工具类型进阶:类型编程进阶
学习时长: 83分3秒
18
基础类型新成员:模板字符串类型入门
学习时长: 32分58秒
19
类型编程新范式:模板字符串工具类型进阶
学习时长: 69分27秒
20
工程层面的类型能力:类型声明、类型指令与命名空间
学习时长: 53分23秒
21
在 React 中愉快地使用 TypeScript:内置类型与泛型坑位
学习时长: 78分9秒
22
让 ESLint 来约束你的 TypeScript 代码:配置与规则集介绍
学习时长: 57分13秒
23
全链路 TypeScript 工具库,找到适合你的工具
学习时长: 25分13秒
24
说说 TypeScript 和 ECMAScript 之间那些事儿
学习时长: 29分14秒
25
装饰器与反射元数据:了解装饰器基本原理与应用
学习时长: 81分53秒
26
控制反转与依赖注入:基于装饰器的依赖注入实现
学习时长: 69分6秒
27
TSConfig 全解(上):构建相关配置
学习时长: 55分33秒
28
TSConfig 全解(下):检查相关、工程相关配置
学习时长: 76分19秒
29
基于 Prisma + NestJs 的 Node API :前置知识储备
学习时长: 44分50秒
30
基于 Prisma + NestJs 的 Node API :项目开发与基于 Heroku 部署
学习时长: 46分40秒
31
玩转 TypeScript AST:AST Checker 与 CodeMod
学习时长: 85分18秒
32
感谢相伴:是结束,也是开始
学习时长: 3分21秒
33
漫谈篇:面试中的 TypeScript
学习时长: 4分25秒
juejin_logo copyCreated with Sketch.

上一节,我们了解了 TypeScript 中的内置类型 any、unknown 与 never,也提到这些内置类型实际上是最基础的“积木”。那想要利用好这些“积木”,我们还需要一些实用的类型工具。它们就像是锤子、锯子和斧子,有了它们的帮助,我们甚至可以拼装出摩天大楼!

在实际的类型编程中,为了满足各种需求下的类型定义,我们通常会结合使用这些类型工具。因此,我们一定要清楚这些类型工具各自的使用方法和功能。

所以,接下来我们会用两节课的时间来聊聊这些类型工具。类型工具顾名思义,它就是对类型进行处理的工具。如果按照使用方式来划分,类型工具可以分成三类:操作符、关键字与专用语法。我们会在这两节中掌握这些不同的使用方式,以及如何去结合地进行使用。

而按照使用目的来划分,类型工具可以分为 类型创建类型安全保护 两类。这一节我们将学习的类型工具就属于类型创建,它们的作用都是基于已有的类型创建新的类型,这些类型工具包括类型别名、交叉类型、索引类型与映射类型。

本节代码见:Internal Type Tools

类型别名

类型别名可以说是 TypeScript 类型编程中最重要的一个功能,从一个简单的函数类型别名,到让你眼花缭乱的类型体操,都离不开类型别名。虽然很重要,但它的使用却并不复杂:

type A = string;

我们通过 type 关键字声明了一个类型别名 A ,同时它的类型等价于 string 类型。类型别名的作用主要是对一组类型或一个特定类型结构进行封装,以便于在其它地方进行复用。

比如抽离一组联合类型:

type StatusCode = 200 | 301 | 400 | 500 | 502;
type PossibleDataTypes = string | number | (() => unknown);

const status: StatusCode = 502;

抽离一个函数类型:

type Handler = (e: Event) => void;

const clickHandler: Handler = (e) => { };
const moveHandler: Handler = (e) => { };
const dragHandler: Handler = (e) => { };

声明一个对象类型,就像接口那样:

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

关于接口和类型别名的取舍,请参考原始类型与对象类型一节。

看起来类型别名真的非常简单,不就是声明了一个变量让类型声明更简洁和易于拆分吗?如果真的只是把它作为类型别名,用来进行特定类型的抽离封装,那的确很简单。然而,类型别名还能作为工具类型。工具类同样基于类型别名,只是多了个泛型

如果你还不了解泛型也无需担心,现阶段我们只要了解它和类型别名相关的使用就可以了。至于更复杂的泛型使用场景,我们后面会详细了解。

在类型别名中,类型别名可以这么声明自己能够接受泛型(我称之为泛型坑位)。一旦接受了泛型,我们就叫它工具类型:

type Factory<T> = T | number | string;

虽然现在类型别名摇身一变成了工具类型,但它的基本功能仍然是创建类型,只不过工具类型能够接受泛型参数,实现更灵活的类型创建功能。从这个角度看,工具类型就像一个函数一样,泛型是入参,内部逻辑基于入参进行某些操作,再返回一个新的类型。比如在上面这个工具类型中,我们就简单接受了一个泛型,然后把它作为联合类型的一个成员,返回了这个联合类型。

const foo: Factory<boolean> = true;

当然,我们一般不会直接使用工具类型来做类型标注,而是再度声明一个新的类型别名:

type FactoryWithBool = Factory<boolean>;

const foo: FactoryWithBool = true;

同时,泛型参数的名称(上面的 T )也不是固定的。通常我们使用大写的 T / K / U / V / M / O ...这种形式。如果为了可读性考虑,我们也可以写成大驼峰形式(即在驼峰命名的基础上,首字母也大写)的名称,比如:

type Factory<NewType> = NewType | number | string;

声明一个简单、有实际意义的工具类型:

type MaybeNull<T> = T | null;

这个工具类型会接受一个类型,并返回一个包括 null 的联合类型。这样一来,在实际使用时就可以确保你处理了可能为空值的属性读取与方法调用:

type MaybeNull<T> = T | null;

function process(input: MaybeNull<{ handler: () => {} }>) {
  input?.handler();
}

类似的还有 MaybePromise、MaybeArray。这也是我在日常开发中最常使用的一类工具类型:

type MaybeArray<T> = T | T[];

// 函数泛型我们会在后面了解~
function ensureArray<T>(input: MaybeArray<T>): T[] {
  return Array.isArray(input) ? input : [input];
}

另外,类型别名中可以接受任意个泛型,以及为泛型指定约束、默认值等,这些内容我们都会在泛型一节深入了解。

总之,对于工具类型来说,它的主要意义是基于传入的泛型进行各种类型操作,得到一个新的类型。而这个类型操作的指代就非常非常广泛了,甚至说类型编程的大半难度都在这儿呢,这也是这本小册占据篇幅最多的部分。

联合类型与交叉类型

在原始类型与对象类型一节,我们了解了联合类型。但实际上,联合类型还有一个和它有点像的孪生兄弟:交叉类型。它和联合类型的使用位置一样,只不过符号是&,即按位与运算符。

实际上,正如联合类型的符号是|,它代表了按位或,即只需要符合联合类型中的一个类型,既可以认为实现了这个联合类型,如A | B,只需要实现 A 或 B 即可。

而代表着按位与的 & 则不同,你需要符合这里的所有类型,才可以说实现了这个交叉类型,即 A & B需要同时满足 A 与 B 两个类型才行。

我们声明一个交叉类型:

interface NameStruct {
  name: string;
}

interface AgeStruct {
  age: number;
}

type ProfileStruct = NameStruct & AgeStruct;

const profile: ProfileStruct = {
  name: "linbudu",
  age: 18
}

很明显这里的 profile 对象需要同时符合这两个对象的结构。从另外一个角度来看,ProfileStruct 其实就是一个新的,同时包含 NameStruct 和 AgeStruct 两个接口所有属性的类型。这里是对于对象类型的合并,那对于原始类型呢?

type StrAndNum = string & number; // never

我们可以看到,它竟然变成 never 了!看起来很奇怪,但想想我们前面给出的定义,新的类型会同时符合交叉类型的所有成员,存在既是 string 又是 number 的类型吗?当然不。实际上,这也是 never 这一 BottomType 的实际意义之一,描述根本不存在的类型嘛。

对于对象类型的交叉类型,其内部的同名属性类型同样会按照交叉类型进行合并:

type Struct1 = {
  primitiveProp: string;
  objectProp: {
    name: string;
  }
}

type Struct2 = {
  primitiveProp: number;
  objectProp: {
    age: number;
  }
}

type Composed = Struct1 & Struct2;

type PrimitivePropType = Composed['primitiveProp']; // never
type ObjectPropType = Composed['objectProp']; // { name: string; age: number; }

如果是两个联合类型组成的交叉类型呢?其实还是类似的思路,既然只需要实现一个联合类型成员就能认为是实现了这个联合类型,那么各实现两边联合类型中的一个就行了,也就是两边联合类型的交集:

type UnionIntersection1 = (1 | 2 | 3) & (1 | 2); // 1 | 2
type UnionIntersection2 = (string | number | symbol) & string; // string

总结一下交叉类型和联合类型的区别就是,联合类型只需要符合成员之一即可(||),而交叉类型需要严格符合每一位成员(&&)。

索引类型

索引类型指的不是某一个特定的类型工具,它其实包含三个部分:索引签名类型索引类型查询索引类型访问。目前很多社区的学习教程并没有这一点进行说明,实际上这三者都是独立的类型工具。唯一共同点是,它们都通过索引的形式来进行类型操作,但索引签名类型是声明,后两者则是读取。接下来,我们来依次介绍三个部分。

索引签名类型

索引签名类型主要指的是在接口或类型别名中,通过以下语法来快速声明一个键值类型一致的类型结构

interface AllStringTypes {
  [key: string]: string;
}

type AllStringTypes = {
  [key: string]: string;
}

这时,即使你还没声明具体的属性,对于这些类型结构的属性访问也将全部被视为 string 类型:

interface AllStringTypes {
  [key: string]: string;
}

type PropType1 = AllStringTypes['linbudu']; // string
type PropType2 = AllStringTypes['599']; // string

在这个例子中我们声明的键的类型为 string([key: string]),这也意味着在实现这个类型结构的变量中只能声明字符串类型的键

interface AllStringTypes {
  [key: string]: string;
}

const foo: AllStringTypes = {
  "linbudu": "599"
}

但由于 JavaScript 中,对于 obj[prop] 形式的访问会将数字索引访问转换为字符串索引访问,也就是说, obj[599]obj['599'] 的效果是一致的。因此,在字符串索引签名类型中我们仍然可以声明数字类型的键。类似的,symbol 类型也是如此:

const foo: AllStringTypes = {
  "linbudu": "599",
  599: "linbudu",
  [Symbol("ddd")]: 'symbol',
}

索引签名类型也可以和具体的键值对类型声明并存,但这时这些具体的键值类型也需要符合索引签名类型的声明:

interface AllStringTypes {
  // 类型“number”的属性“propA”不能赋给“string”索引类型“boolean”。
  propA: number;
  [key: string]: boolean;
}

这里的符合即指子类型,因此自然也包括联合类型:

interface StringOrBooleanTypes {
  propA: number;
  propB: boolean;
  [key: string]: number | boolean;
}

索引签名类型的一个常见场景是在重构 JavaScript 代码时,为内部属性较多的对象声明一个 any 的索引签名类型,以此来暂时支持对类型未明确属性的访问,并在后续一点点补全类型:

interface AnyTypeHere {
  [key: string]: any;
}

const foo: AnyTypeHere['linbudu'] = 'any value';

索引类型查询

刚才我们已经提到了索引类型查询,也就是 keyof 操作符。严谨地说,它可以将对象中的所有键转换为对应字面量类型,然后再组合成联合类型。注意,这里并不会将数字类型的键名转换为字符串类型字面量,而是仍然保持为数字类型字面量

interface Foo {
  linbudu: 1,
  599: 2
}

type FooKeys = keyof Foo; // "linbudu" | 599

如果觉得不太好理解,我们可以写段伪代码来模拟 “从键名到联合类型” 的过程。

type FooKeys = Object.keys(Foo).join(" | ");

除了应用在已知的对象类型结构上以外,你还可以直接 keyof any 来生产一个联合类型,它会由所有可用作对象键值的类型组成:string | number | symbol。也就是说,它是由无数字面量类型组成的,由此我们可以知道, keyof 的产物必定是一个联合类型

索引类型访问

在 JavaScript 中我们可以通过 obj[expression] 的方式来动态访问一个对象属性(即计算属性),expression 表达式会先被执行,然后使用返回值来访问属性。而 TypeScript 中我们也可以通过类似的方式,只不过这里的 expression 要换成类型。接下来,我们来看个例子:

interface NumberRecord {
  [key: string]: number;
}

type PropType = NumberRecord[string]; // number

这里,我们使用 string 这个类型来访问 NumberRecord。由于其内部声明了数字类型的索引签名,这里访问到的结果即是 number 类型。注意,其访问方式与返回值均是类型。

更直观的例子是通过字面量类型来进行索引类型访问:

interface Foo {
  propA: number;
  propB: boolean;
}

type PropAType = Foo['propA']; // number
type PropBType = Foo['propB']; // boolean

看起来这里就是普通的值访问,但实际上这里的'propA''propB'都是字符串字面量类型而不是一个 JavaScript 字符串值。索引类型查询的本质其实就是,通过键的字面量类型('propA')访问这个键对应的键值类型(number

看到这你肯定会想到,上面的 keyof 操作符能一次性获取这个对象所有的键的字面量类型,是否能用在这里?当然,这可是 TypeScript 啊。

interface Foo {
  propA: number;
  propB: boolean;
  propC: string;
}

type PropTypeUnion = Foo[keyof Foo]; // string | number | boolean

使用字面量联合类型进行索引类型访问时,其结果就是将联合类型每个分支对应的类型进行访问后的结果,重新组装成联合类型。索引类型查询、索引类型访问通常会和映射类型一起搭配使用,前两者负责访问键,而映射类型在其基础上访问键值类型,我们在下面一个部分就会讲到。

注意,在未声明索引签名类型的情况下,我们不能使用 NumberRecord[string] 这种原始类型的访问方式,而只能通过键名的字面量类型来进行访问。

interface Foo {
  propA: number;
}

// 类型“Foo”没有匹配的类型“string”的索引签名。
type PropAType = Foo[string]; 

索引类型的最佳拍档之一就是映射类型,同时映射类型也是类型编程中常用的一个手段。

映射类型:类型编程的第一步

不同于索引类型包含好几个部分,映射类型指的就是一个确切的类型工具。看到映射这个词你应该能联想到 JavaScript 中数组的 map 方法,实际上也是如此,映射类型的主要作用即是基于键名映射到键值类型。概念不好理解,我们直接来看例子:

type Stringify<T> = {
  [K in keyof T]: string;
};

这个工具类型会接受一个对象类型(假设我们只会这么用),使用 keyof 获得这个对象类型的键名组成字面量联合类型,然后通过映射类型(即这里的 in 关键字)将这个联合类型的每一个成员映射出来,并将其键值类型设置为 string。

具体使用的表现是这样的:

interface Foo {
  prop1: string;
  prop2: number;
  prop3: boolean;
  prop4: () => void;
}

type StringifiedFoo = Stringify<Foo>;

// 等价于
interface StringifiedFoo {
  prop1: string;
  prop2: string;
  prop3: string;
  prop4: string;
}

我们还是可以用伪代码的形式进行说明:

const StringifiedFoo = {};
for (const k of Object.keys(Foo)){
  StringifiedFoo[k] = string;
}

看起来好像很奇怪,我们应该很少会需要把一个接口的所有属性类型映射到 string?这有什么意义吗?别忘了,既然拿到了键,那键值类型其实也能拿到:

type Clone<T> = {
  [K in keyof T]: T[K];
};

这里的T[K]其实就是上面说到的索引类型访问,我们使用键的字面量类型访问到了键值的类型,这里就相当于克隆了一个接口。需要注意的是,这里其实只有K in 属于映射类型的语法,keyof T 属于 keyof 操作符,[K in keyof T][]属于索引签名类型,T[K]属于索引类型访问。

总结与预告

这一节,我们认识了类型工具中的类型别名、联合类型、索引类型以及映射类型。这些工具代表了类型工具中用于创建新类型的部分,但它们实现创建的方式却五花八门,以下这张表格概括了它们的实现方式与常见搭配

类型工具创建新类型的方式常见搭配
类型别名(Type Alias)将一组类型/类型结构封装,作为一个新的类型联合类型、映射类型
工具类型(Tool Type)在类型别名的基础上,基于泛型去动态创建新类型基本所有类型工具
联合类型(Union Type)创建一组类型集合,满足其中一个类型即满足这个联合类型(||)类型别名、工具类型
交叉类型(Intersection Type)创建一组类型集合,满足其中所有类型才满足映射联合类型(&&)类型别名、工具类型
索引签名类型(Index Signature Type)声明一个拥有任意属性,键值类型一致的接口结构映射类型
索引类型查询(Indexed Type Query)从一个接口结构,创建一个由其键名字符串字面量组成的联合类型映射类型
索引类型访问(Indexed Access Type)从一个接口结构,使用键名字符串字面量访问到对应的键值类型类型别名、映射类型
映射类型 (Mapping Type)从一个联合类型依次映射到其内部的每一个类型工具类型

在下一节,我们会继续来介绍类型工具中的类型查询操作符 typeof 以及类型守卫,如果说这一节我们了解的工具主要是生产新的类型,那类型守卫就像是流水线的质量检查员一样,它可以帮助你的代码进一步提升类型安全性。

留言
Ctrl + Enter
全部评论(70)
fendiman的头像
删除
打卡
点赞
回复
2017n8y27r8d40f的头像
删除
请问为什么原始类型string通过keyof 会出现number呢?
image
点赞
1
删除
我理解是这样的,不知道对不对

let str = "this is a string"
str[0]
// t
str[2]
// i

可以想象 string 这个原始类型是这么定义的
{
toString: xxx
......
[key: number]: xxxx
}
收起
2
回复
mylyh的头像
删除
"克隆了一个接口", 你这里克隆了一个接口又有什么意义呢
点赞
2
删除
能不能多举几个实际项目的使用场景
点赞
回复
删除
(作者)
回复
实际场景后面有,可以先看完哈
能不能多举几个实际项目的使用场景
1
回复
嘎嘎🙊的头像
删除
打卡
点赞
回复
逆风.微笑的头像
删除
web @ naver china
type MaybeNull<T> = T | null; 这里大佬是不是想表达可能为空?如果是的话,用type MaybeNull<T> = T | void; 在调用process函数时,不传参数才不会报错。
1
1
删除
(作者)
为空和为空值是两个概念,可以参考原始类型一节的说明
1
回复
多吃牛肉减肥的头像
删除
映射类型,这个in 是不是遍历的意思
点赞
1
删除
是的 跟map foreach 类似
点赞
回复
用户2362677620139的头像
删除
打卡
点赞
回复
_梟的头像
删除
总结表格里这两个”字符串“是不是应该去掉?
image
点赞
回复
_梟的头像
删除
"索引签名类型也可以和具体的键值对类型声明并存"
"在字符串索引签名类型中我们仍然可以声明数字类型的键。类似的,symbol 类型也是如此"

基于文章【索引签名类型】那块说到的上面这两句,我给原例子加了一个Symbol为键的属性,但是却报错了,按上面两句所说,symbol类型的键在这里应该算是字符串所兼容的吧?为啥会报错呢?
例:
interface AllStringTypes {

// 报错信息:A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type.(1169)

[Symbol("prop")]: number,
[key: string]: number;

}
收起
点赞
2
删除
(作者)
这个和报错和索引签名类型无关,声明一个unique symbol类型的变量作为计算属性即可
点赞
回复
删除
噢噢,我知道哪里错了..谢谢回复。
这个和报错和索引签名类型无关,声明一个unique symbol类型的变量作为计算属性即可
点赞
回复
前端菜菜籽的头像
删除
前端工程师 @ 东岛菜菜子公司
[赞][赞]讲的很好!容易理解。
点赞
回复
anonymity94的头像
删除
CV工程师 @ 字节跳动
不渡哥哥真的好棒~[舔屏]
点赞
回复
Geek喜多川的头像
删除
面向工资💰编程 @ 人生不设限公司
”字符串字面量类型“和”JavaScript 字符串值“这部分,没太理解字符串字面量类型是啥?[疑问]
点赞
1
删除
使用type来声明字符串字面量类型,但是不仅限于字符串

字符串字面量类型用来约束取值只能是某几个字符串中的一个。
点赞
回复
今日好运来的头像
删除
前端开发 @ 科技公司
不渡大佬写的真的很详细,由浅入深的讲了类型别名,联合类型,交叉类型,泛型,再引申到索引类型签名,索引类型引用,索引类型查询,最后再将索引类名和泛型结合起来,真的没那么难懂了[呲牙]
1
回复
樱桃小锤子的头像
删除
前端 @ 一只转行的咸鱼
打卡!
点赞
回复
没名字的某某人的头像
删除
初级cv工程师 @ 不知名小公司
打卡,系统,且容易理解[赞]
点赞
回复
寿司八哥的头像
删除
Web3D @ 图形起源
打卡,准备二刷
1
回复
你割了吗的头像
删除
前端割图仔 @ 割割更健康
[看]照作者这个讲法 我感觉我能学会
2
回复
__小东__的头像
删除
前端 @ 360金融
打开
点赞
回复
涛涛_江的头像
删除
打卡
点赞
回复
方阿森的头像
删除
前端 @ 蓝湖
下面这种情况有解释吗。我看了下 如果 keyof 一个 string,是可以得到一个联合类型的,但是放到 Stringify 里面返回的类型还是 string。我理解应该是一个新的对象类型,里面每一个键对应的值都是 string 吧。
image
点赞
1
删除
(作者)
这个我的理解是TS内部对映射类型的兜底逻辑,因为觉得会增加理解成本就没带上。这个行为会出现在使用原始类型、[]字面量等这种”无法“进行 keyof 查询的类型的情况下,因此认为它是类似兜底的行为,实际使用一般会约束泛型来避免这种情况。
点赞
回复
方阿森的头像
删除
前端 @ 蓝湖
可以列出这些类型对应的英文名称吗?
点赞
1
删除
(作者)
已添加~
1
回复
ylhao666的头像
删除
keyof 的产物必定是一个联合类型。

不对吧
image
2
4
删除
测试是你那样测试的嘛,搞不懂
image
点赞
回复
删除
没毛病吧,不一定是联合类型?
测试是你那样测试的嘛,搞不懂
点赞
回复
删除
大佬说什么就是什么啦[发呆]
没毛病吧,不一定是联合类型?
点赞
回复
删除
你这个案例的有没有一种可能 keyof出来的是number | number| .... |number只有number这一个类型捏
没毛病吧,不一定是联合类型?
1
回复
用户5296370708132的头像
删除
这些专业名词好难记
点赞
回复
Kuroo的头像
删除
前端开发
系统、专业!
点赞
回复
沉江河的头像
删除
端水大师 @ 松花江畔
[666]
点赞
回复
阳树阳树的头像
删除
蔚来@FE前端
打卡打卡
点赞
回复
风之精灵王的头像
删除
写的很棒,干货很多,很多都是掘金文章看不到的东西
点赞
回复
萨博尔的头像
删除
前端开发
666
点赞
回复
guaishou的头像
删除
da
点赞
回复
狒狒君14014的头像
删除
前端工程师
[机智][机智][机智]写得太好了,非常有条理!!!!
点赞
1
删除
确实,有自己的东西,不是简单的复制粘贴文档
点赞
回复
Revol_C的头像
删除
前端开发
请问在映射类型中,in 关键字后面应该是接一个 string | number | symbol(及其子类型)的联合类型吗?
image
点赞
回复
卷王归来的头像
删除
web前端开发工程师
打卡
点赞
回复
ainuo5213的头像
删除
前后端 @ 招商银行
可以,我算是明白了usevModel这个方法为啥ts会直接提示,如果没有海还报错
点赞
回复
Revol_C的头像
删除
前端开发
请问symbol类型的键值是必须要用[]包裹吗
image
点赞
1
删除
(作者)
是的,本质也是一次计算属性的求值
点赞
回复
在摸了在摸了的头像
删除
切图仔
打卡
点赞
回复
远山眉u的头像
删除
前端开发工程师
点赞
回复
前端BB机的头像
删除
前端开发 @ 米奇妙妙屋
看懂了,但是文中各种的名词我都记不住和分不清,但是我确实会用这些ts方法了[呲牙]
点赞
回复
Lamzzz的头像
删除
请问索引签名类型中,为啥字符串索引会兼容 symbol 索引?
1
1
删除
同问,为啥keyof字符串索引,又没有symbol
点赞
回复
Big_Rice的头像
删除
前端工程师 @ 未知
打卡
点赞
回复
用户5457771076的头像
删除
打卡
点赞
回复
肥柴前端小菜鸡的头像
删除
文章里面伪代码的形式进行说明这些部分!!!很喜欢[嘿哈]
1
回复
furfurJiang的头像
删除
前端 @ 程序江
条理清晰,结合例子,很好理解[玫瑰]
点赞
回复
codeplays的头像
删除
头晕了已经
点赞
回复
Snowb的头像
删除
我已经晕了..... 打算再看几遍
1
1
删除
其实有时候,写得名词太多不适合理解,看官网对照学习。
点赞
回复
AirHua的头像
删除
冒牌工程师,业余摄影迷,菜鸟吉他酥.
这里键值类型一定是一致的吗,前面好像有不一致的例子,有些不太懂
image
点赞
1
删除
(作者)
一致指的是同一个类型,当然也包括联合类型,举例来说如果你设置索引签名类型是 string | number,这个时候你的属性即可以是 string 也可以是 number,但它们都还是符合索引签名类型,所以说一致
点赞
回复
解冻的头像
删除
nmber 笔误[看]
image
点赞
1
删除
(作者)
已修正~
点赞
回复
饿了么黄金骑手的头像
删除
一个前端 @ 海康威视
不错不错,点赞~
点赞
回复
一骑绝尘sixknow的头像
删除
看完
点赞
回复
zhedream的头像
删除
全栈工程师 @ @zhedream
已学完
点赞
回复
codermao的头像
删除
web实习 @ 小红书
不错不错,又是有收获的一天[不失礼貌的微笑]
点赞
回复
The action has been successful