03-进入类型的世界:理解原始类型与对象类型
课程
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.

通常来说,学习一件新事物的较好方式是和你已掌握的做对比,通过二者之间通用的概念帮你快速熟悉新的事物。比如,在掌握了 Vue 以后再去学习 React,对于组件通信、状态管理、插槽等这些二者共有的概念,你会感到非常熟悉。同样的,这一章我们会从 JavaScript 的既有概念开始学习,详细讲解 TypeScript 所有原始类型、数组以及对象的类型标注,让你能快速对 TypeScript 的功能、语法有一个基础认知。

这一章会包含几乎所有与原始类型、对象类型、数组等强相关的知识点,这么设计主要是因为它们之间的内容环环相扣,而且也不难,放在一起能帮助你一次性建立相对完整的知识体系

如果你已经对这一章的知识点有基本了解或者非常熟悉了,那我建议你利用它来查缺补漏,为后面的学习打好基础,比如元组就是一个很容易被大家忽视的知识点。

话不多说,跟着我一起往下看吧!

本节代码见:Primitive and Object

原始类型的类型标注

首先,我们来看 JavaScript的内置原始类型。除了最常见的 number / string / boolean / null / undefined, ECMAScript 2015(ES6)、2020 (ES11) 又分别引入了 2 个新的原始类型:symbol 与 bigint 。在 TypeScript 中它们都有对应的类型注解:

const name: string = 'linbudu';
const age: number = 24;
const male: boolean = false;
const undef: undefined = undefined;
const nul: null = null;
const obj: object = { name, age, male };
const bigintVar1: bigint = 9007199254740991n;
const bigintVar2: bigint = BigInt(9007199254740991);
const symbolVar: symbol = Symbol('unique');

其中,除了 null 与 undefined 以外,余下的类型基本上可以完全对应到 JavaScript 中的数据类型概念,因此这里我们只对 null 与 undefined 展开介绍。

null 与 undefined

在 JavaScript 中,null 与 undefined 分别表示“这里有值,但是个空值”和“这里没有值”。而在 TypeScript 中,null 与 undefined 类型都是有具体意义的类型。也就是说,它们作为类型时,表示的是一个有意义的具体类型值。这两者在没有开启 strictNullChecks 检查的情况下,会被视作其他类型的子类型,比如 string 类型会被认为包含了 null 与 undefined 类型:

const tmp1: null = null;
const tmp2: undefined = undefined;

const tmp3: string = null; // 仅在关闭 strictNullChecks 时成立,下同
const tmp4: string = undefined;

除了上面介绍的原始类型以及 null、undefined 类型以外,在 TypeScript 中还存在着一个特殊的类型:void,它和 JavaScript 中的 void 同样不是一回事,我们接着往下看。

void

你是否看到过以下的 JavaScript 代码呢?

<a href="javascript:void(0)">清除缓存</a>

这里的 void(0) 等价于 void 0,即 void expression 的语法。void 操作符会执行后面跟着的表达式并返回一个 undefined,如你可以使用它来执行一个立即执行函数(IIFE):

void function iife() {
  console.log("Invoked!");
}();

能这么做是因为,void 操作符强制将后面的函数声明转化为了表达式,因此整体其实相当于:void((function iife(){})())

事实上,TypeScript 的原始类型标注中也有 void,但与 JavaScript 中不同的是,这里的 void 用于描述一个内部没有 return 语句,或者没有显式 return 一个值的函数的返回值,如:

function func1() {}
function func2() {
  return;
}
function func3() {
  return undefined;
}

在这里,func1 与 func2 的返回值类型都会被隐式推导为 void,只有显式返回了 undefined 值的 func3 其返回值类型才被推导为了 undefined。但在实际的代码执行中,func1 与 func2 的返回值均是 undefined。

虽然 func3 的返回值类型会被推导为 undefined,但是你仍然可以使用 void 类型进行标注,因为在类型层面 func1、func2、func3 都表示“没有返回一个有意义的值”。

这里可能有点绕,你可以认为 void 表示一个空类型,而 null 与 undefined 都是一个具有意义的实际类型(注意与它们在 JavaScript 中的意义区分)。而 undefined 能够被赋值给 void 类型的变量,就像在 JavaScript 中一个没有返回值的函数会默认返回一个 undefined 。null 类型也可以,但需要在关闭 strictNullChecks 配置的情况下才能成立。

const voidVar1: void = undefined;

const voidVar2: void = null; // 需要关闭 strictNullChecks

到这里,我们了解了 JavaScript 中原始数据类型到 TypeScript 原始类型概念地映射,你应当能感觉到 TypeScript 对 JavaScript 开发者的友好,大部分概念都能比较自然地过渡,下面的数组与对象的类型标注同样如此。

数组的类型标注

数组同样是我们最常用的类型之一,在 TypeScript 中有两种方式来声明一个数组类型:

const arr1: string[] = [];

const arr2: Array<string> = [];

这两种方式是完全等价的,但其实更多是以前者为主,如果你将鼠标悬浮在 arr2 上,会发现它显示的类型签名是 string[]。数组是我们在日常开发大量使用的数据结构,但在某些情况下,使用 元组(Tuple) 来代替数组要更加妥当,比如一个数组中只存放固定长度的变量,但我们进行了超出长度地访问:

const arr3: string[] = ['lin', 'bu', 'du'];

console.log(arr3[599]);

这种情况肯定是不符合预期的,因为我们能确定这个数组中只有三个成员,并希望在越界访问时给出类型报错。这时我们可以使用元组类型进行类型标注:

const arr4: [string, string, string] = ['lin', 'bu', 'du'];

console.log(arr4[599]);

此时将会产生一个类型错误:长度为“3”的元组类型“[string, string, string]”在索引“599“处没有元素。除了同类型的元素以外,元组内部也可以声明多个与其位置强绑定的,不同类型的元素:

const arr5: [string, number, boolean] = ['linbudu', 599, true];

在这种情况下,对数组合法边界内的索引访问(即 0、1、2)将精确地获得对应位置上的类型。同时元组也支持了在某一个位置上的可选成员:

const arr6: [string, number?, boolean?] = ['linbudu'];
// 下面这么写也可以
// const arr6: [string, number?, boolean?] = ['linbudu', , ,];

对于标记为可选的成员,在 --strictNullCheckes 配置下会被视为一个 string | undefined 的类型。此时元组的长度属性也会发生变化,比如上面的元组 arr6 ,其长度的类型为 1 | 2 | 3

type TupleLength = typeof arr6.length; // 1 | 2 | 3

也就是说,这个元组的长度可能为 1、2、3。

关于类型别名(type)、类型查询(typeof)以及联合类型,我们会在后面讲到,这里你只需要简单了解即可。

你可能会觉得,元组的可读性实际上并不好。比如对于 [string, number, boolean]来说,你并不能直接知道这三个元素都代表什么,还不如使用对象的形式。而在 TypeScript 4.0 中,有了具名元组(Labeled Tuple Elements)的支持,使得我们可以为元组中的元素打上类似属性的标记:

const arr7: [name: string, age: number, male: boolean] = ['linbudu', 599, true];

有没有很酷?考虑到某些拼装对象太麻烦,我们完全可以使用具名元组来做简单替换。具名元组可选元素的修饰符将成为以下形式:

const arr7: [name: string, age: number, male?: boolean] = ['linbudu', 599, true];

实际上除了显式地越界访问,还可能存在隐式地越界访问,如通过解构赋值的形式:

const arr1: string[] = [];

const [ele1, ele2, ...rest] = arr1;

对于数组,此时仍然无法检查出是否存在隐式访问,因为类型层面并不知道它到底有多少个元素。但对于元组,隐式的越界访问也能够被揪出来给一个警告:

const arr5: [string, number, boolean] = ['linbudu', 599, true];

// 长度为 "3" 的元组类型 "[string, number, boolean]" 在索引 "3" 处没有元素。
const [name, age, male, other] = arr5;

JavaScript 的开发者对元组 Tuple 的概念可能比较陌生,毕竟在 JavaScript 中我们很少声明定长的数组。但使用元组确实能帮助我们进一步提升数组结构的严谨性,包括基于位置的类型标注、避免出现越界访问等等。除了通过数组类型提升数组结构的严谨性,TypeScript 中的对象类型也能帮助我们提升对象结构的严谨性。接下来我们就一起来看看。

对象的类型标注

作为 JavaScript 中使用最频繁的数据结构,对象的类型标注是我们本节要重点关注的部分。接下来我们会学习如何在 TypeScript 中声明对象、修饰对象属性,以及了解可能存在的使用误区。这些内容能够帮助你建立起对 TypeScript 中立体类型(我们可以理解为前面的原始类型是“平面类型”)的了解,正式入门 TypeScript 。

类似于数组类型,在 TypeScript 中我们也需要特殊的类型标注来描述对象类型,即 interface ,你可以理解为它代表了这个对象对外提供的接口结构。

首先我们使用 interface 声明一个结构,然后使用这个结构来作为一个对象的类型标注即可:

interface IDescription {
  name: string;
  age: number;
  male: boolean;
}

const obj1: IDescription = {
  name: 'linbudu',
  age: 599,
  male: true,
};

这里的“描述”指:

  • 每一个属性的值必须一一对应到接口的属性类型

  • 不能有多的属性,也不能有少的属性,包括直接在对象内部声明,或是 obj1.other = 'xxx' 这样属性访问赋值的形式

除了声明属性以及属性的类型以外,我们还可以对属性进行修饰,常见的修饰包括可选(Optional)只读(Readonly) 这两种。

修饰接口属性

类似于上面的元组可选,在接口结构中同样通过 ? 来标记一个属性为可选:

interface IDescription {
  name: string;
  age: number;
  male?: boolean;
  func?: Function;
}

const obj2: IDescription = {
  name: 'linbudu',
  age: 599,
  male: true,
  // 无需实现 func 也是合法的
};

在这种情况下,即使你在 obj2 中定义了 male 属性,但当你访问 obj2.male 时,它的类型仍然会是 boolean | undefined,因为毕竟这是我们自己定义的类型嘛。

假设新增一个可选的函数类型属性,然后进行调用:obj2.func() ,此时将会产生一个类型报错:不能调用可能是未定义的方法。但可选属性标记不会影响你对这个属性进行赋值,如:

obj2.male = false;
obj2.func = () => {};

即使你对可选属性进行了赋值,TypeScript 仍然会使用接口的描述为准进行类型检查,你可以使用类型断言、非空断言或可选链解决(别急,我们在后面会讲到)。

除了标记一个属性为可选以外,你还可以标记这个属性为只读:readonly。很多同学对这一关键字比较陌生,因为以往 JavaScript 中并没有这一类概念,它的作用是防止对象的属性被再次赋值

interface IDescription {
  readonly name: string;
  age: number;
}

const obj3: IDescription = {
  name: 'linbudu',
  age: 599,
};

// 无法分配到 "name" ,因为它是只读属性
obj3.name = "林不渡";

其实在数组与元组层面也有着只读的修饰,但与对象类型有着两处不同。

  • 你只能将整个数组/元组标记为只读,而不能像对象那样标记某个属性为只读。

  • 一旦被标记为只读,那这个只读数组/元组的类型上,将不再具有 push、pop 等方法(即会修改原数组的方法),因此报错信息也将是类型 xxx 上不存在属性“push”这种。这一实现的本质是只读数组与只读元组的类型实际上变成了 ReadonlyArray,而不再是 Array。

type 与 interface

我也知道,很多同学更喜欢用 type(Type Alias,类型别名)来代替接口结构描述对象,而我更推荐的方式是,interface 用来描述对象、类的结构,而类型别名用来将一个函数签名、一组联合类型、一个工具类型等等抽离成一个完整独立的类型。但大部分场景下接口结构都可以被类型别名所取代,因此,只要你觉得统一使用类型别名让你觉得更整齐,也没什么问题。

object、Object 以及 { }

objectObject 以及{}(一个空对象)这三者的使用可能也会让部分同学感到困惑,所以我也专门解释下。

首先是 Object 的使用。被 JavaScript 原型链折磨过的同学应该记得,原型链的顶端是 Object 以及 Function,这也就意味着所有的原始类型与对象类型最终都指向 Object,在 TypeScript 中就表现为 Object 包含了所有的类型:

// 对于 undefined、null、void 0 ,需要关闭 strictNullChecks
const tmp1: Object = undefined;
const tmp2: Object = null;
const tmp3: Object = void 0;

const tmp4: Object = 'linbudu';
const tmp5: Object = 599;
const tmp6: Object = { name: 'linbudu' };
const tmp7: Object = () => {};
const tmp8: Object = [];

和 Object 类似的还有 Boolean、Number、String、Symbol,这几个装箱类型(Boxed Types) 同样包含了一些超出预期的类型。以 String 为例,它同样包括 undefined、null、void,以及代表的 拆箱类型(Unboxed Types) string,但并不包括其他装箱类型对应的拆箱类型,如 boolean 与 基本对象类型,我们看以下的代码:

const tmp9: String = undefined;
const tmp10: String = null;
const tmp11: String = void 0;
const tmp12: String = 'linbudu';

// 以下不成立,因为不是字符串类型的拆箱类型
const tmp13: String = 599; // X
const tmp14: String = { name: 'linbudu' }; // X
const tmp15: String = () => {}; // X
const tmp16: String = []; // X

在任何情况下,你都不应该使用这些装箱类型。

object 的引入就是为了解决对 Object 类型的错误使用,它代表所有非原始类型的类型,即数组、对象与函数类型这些

const tmp17: object = undefined;
const tmp18: object = null;
const tmp19: object = void 0;

const tmp20: object = 'linbudu';  // X 不成立,值为原始类型
const tmp21: object = 599; // X 不成立,值为原始类型

const tmp22: object = { name: 'linbudu' };
const tmp23: object = () => {};
const tmp24: object = [];

最后是{},一个奇奇怪怪的空对象,如果你了解过字面量类型,可以认为{}就是一个对象字面量类型(对应到字符串字面量类型这样)。否则,你可以认为使用{}作为类型签名就是一个合法的,但内部无属性定义的空对象,这类似于 Object(想想 new Object()),它意味着任何非 null / undefined 的值:


const tmp25: {} = undefined; // 仅在关闭 strictNullChecks 时成立,下同
const tmp26: {} = null;
const tmp27: {} = void 0; // void 0 等价于 undefined

const tmp28: {} = 'linbudu';
const tmp29: {} = 599;
const tmp30: {} = { name: 'linbudu' };
const tmp31: {} = () => {};
const tmp32: {} = [];

虽然能够将其作为变量的类型,但你实际上无法对这个变量进行任何赋值操作

const tmp30: {} = { name: 'linbudu' };

tmp30.age = 18; // X 类型“{}”上不存在属性“age”。

这是因为它就是纯洁的像一张白纸一样的空对象,上面没有任何的属性(除了 toString 这种与生俱来的)。在类型层级一节我们还会再次见到它,不过那个时候它已经被称为“万物的起源”了。

最后,为了更好地区分 Objectobject 以及{}这三个具有迷惑性的类型,我们再做下总结:

  • 在任何时候都不要,不要,不要使用 Object 以及类似的装箱类型。

  • 当你不确定某个变量的具体类型,但能确定它不是原始类型,可以使用 object。但我更推荐进一步区分,也就是使用 Record<string, unknown>Record<string, any> 表示对象,unknown[]any[] 表示数组,(...args: any[]) => any表示函数这样。

  • 我们同样要避免使用{}{}意味着任何非 null / undefined 的值,从这个层面上看,使用它和使用 any 一样恶劣。

总结与预告

这一节,我们一起学习了 TypeScript 中原始类型、对象类型、数组(元组)的类型标注,以及对数组的只读、对象类型属性的访问性修饰。这里的知识其实可以分为两类:

  • 与 JavaScript 概念基本一致的部分,如原始类型与数组类型需要重点掌握,但因为思维方式基本没有变化,所以你可以认为你就是在写更严格一些的 JavaScript
  • 一些全新的概念,比如元组与 readonly 修饰等,这一部分你可能不会很快适应,需要稍微转换一下思维方式。我建议你可以从现在开始,有意识地在日常开发中去多多使用它们。

另外,对于 readonly 这一修饰符,JavaScript 开发者可能需要一定的时间来理解和习惯,但它在工程层面确实是非常推荐的一种实践,可以使用只读标记来避免数组和对象被错误修改。当然,TypeScript 目前只能够帮助你在编译时做检查,类型信息在编译后都会被擦除,所以 readonly 并不会在实际运行时报错。

学习完这一小节后,不妨找出你曾经的 JavaScript 项目,试试用本章学到的知识为这些 JavaScript 代码添加一些类型,再把某些场景下的数组换成元组,为部分对象类型的属性添加 readonly,来感受 TypeScript 代码的严格之美。

在下一节我们要介绍的字面量类型以及枚举,在某些方面其实可以理解为是原始类型与对象类型的进一步延伸,也同样是日常会被重度使用的语法。在完成下一节的学习后,你就可以开始进一步地改造你的 JavaScript 项目,让那些类型变得更精确一些!

扩展阅读

unique symbol

Symbol 在 JavaScript 中代表着一个唯一的值类型,它类似于字符串类型,可以作为对象的属性名,并用于避免错误修改 对象 / Class 内部属性的情况。而在 TypeScript 中,symbol 类型并不具有这一特性,一百个具有 symbol 类型的对象,它们的 symbol 类型指的都是 TypeScript 中的同一个类型。为了实现“独一无二”这个特性,TypeScript 中支持了 unique symbol 这一类型声明,它是 symbol 类型的子类型,每一个 unique symbol 类型都是独一无二的。

const uniqueSymbolFoo: unique symbol = Symbol("linbudu")

// 类型不兼容
const uniqueSymbolBar: unique symbol = uniqueSymbolFoo

在 JavaScript 中,我们可以用 Symbol.for 方法来复用已创建的 Symbol,如 Symbol.for("linbudu") 会首先查找全局是否已经有使用 linbudu 作为 key 的 Symbol 注册,如果有,则返回这个 Symbol,否则才会创建新的 Symbol 。

在 TypeScript 中,如果要引用已创建的 unique symbol 类型,则需要使用类型查询操作符 typeof :

declare const uniqueSymbolFoo: unique symbol;

const uniqueSymbolBaz: typeof uniqueSymbolFoo = uniqueSymbolFoo

以上代码实际执行时会报错,这是因为 uniqueSymbolFoo 是一个仅存在于类型空间的值,这里只是为了进行示例~

这里的 declare、typeof 等使用,都会在后面有详细地讲解。同时 unique symbol 在日常开发的使用非常少见,这里做了解就好~

留言
Ctrl + Enter
全部评论(99)
遇你相识的头像
删除
前端开发工程师 @ 上海哔哩哔哩科技有限公司
打卡,再复习复习。
点赞
回复
HankLi的头像
删除
打卡,打卡 严谨之美[呲牙]
点赞
回复
mylyh的头像
删除
因此报错信息也将是类型 xxx 上不存在属性“push”这种 --- 好像没报错
点赞
回复
举个栗子儿的头像
删除
前端开发工程师 @ 某小型互联网公司
打卡[庆祝]
1
回复
锅包又的头像
删除
白嫖成功![看]
点赞
回复
嘎嘎🙊的头像
删除
打卡[力量]
点赞
回复
朱俊宇的头像
删除
前端开发
学习了
点赞
回复
Zicxxciz的头像
删除
前端工程师 @ ****
打卡
点赞
回复
东东吖的头像
删除
前端工程师
打卡
1
3
删除
东东
点赞
回复
删除
喵呜
东东
点赞
回复
删除
在呢在呢
喵呜
点赞
回复
用户2362677620139的头像
删除
打卡
点赞
回复
codeMax的头像
删除
前端工程师 @ 阿巴阿巴
很清晰,第三章攻略结束[赞]
点赞
回复
Chin的头像
删除
学生
打卡,看懂了30%
点赞
回复
代码迷途的头像
删除
大佬是女生?[赞][赞][赞]
点赞
1
删除
大佬都有女朋友了[捂脸]
点赞
回复
卷王归来的头像
删除
web前端开发工程师
作者你好,还有个KeyVal这个是不是也可以放在object/Object/{}一起讲下 ?
点赞
回复
寿司八哥的头像
删除
Web3D @ 图形起源
打卡
点赞
回复
行僧的头像
删除
这样的操作是不是元组存在漏洞?
image
3
5
删除
push是 追加新的,所以应该不会触发元组的判断....
点赞
回复
删除
好问题
1
回复
删除
那我们如何放心使用元组呢?禁用push,pop么?
push是 追加新的,所以应该不会触发元组的判断....
点赞
回复
删除
目前可行的方案就是用只读元组,会禁用掉push,pop。但这样无法确定对应下标的类型,因此最好的办法似乎只能是人为约定,元组类型禁用push,pop的方式最为稳妥。当然我目前探讨的元组场景是把本来放在对象的数据用元组存储。
那我们如何放心使用元组呢?禁用push,pop么?
点赞
回复
删除
另外 push 本身也会有类型校验的,这里之所以不报错的原因是它校验的时候会判断该类型是否包含在你当前元素类型中,如果不包含,它也会报错。
image
push是 追加新的,所以应该不会触发元组的判断....
点赞
回复
帅得乱七八糟的头像
删除
第二遍,对ts理解更深了
点赞
回复
雨飞飞雨的头像
删除
前端
可以在最后加一个练习环节,放几道练习题
点赞
回复
__小东__的头像
删除
前端 @ 360金融
讲的还可以~!
点赞
回复
没有头罩的头盔的头像
删除
前端开发
打卡
点赞
回复
前端摸鱼儿的头像
删除
为什么在任何情况下不能使用装箱类型呢?
点赞
1
删除
(作者)
装箱类型能够包含对象类型,比如一个{}如果实现了所有字符串方法就可以被视为String类型
1
回复
学会学会的头像
删除
前端小白
打卡 通俗易懂👍
点赞
回复
涛涛_江的头像
删除
打卡
点赞
回复
这个世界产出的bug的头像
删除
请教一下
为何我写了const a:number =1 这样一行代码 按下ctrl+s 以后 编辑器给我自动变成了 const a = 1, 但是如果我写的是 const a:string[] = [] ,编辑器又不会改动代码了,我用的是HbuildX
点赞
6
删除
(作者)
应该是编辑器带了 ESLint 保存修复,第一种情况类型是可以被推导出来的,有一条规则是不允许推导出的类型和标注类型相同,认为是冗余的
点赞
回复
删除
还有这里
interface IDescription {
name: string;
age: number;
male?: boolean;
func?: Function;
}

会报
error Don't use `Function` as a type. The `Function` type accepts any function-like value.
It provides no type safety when calling the function, which can be a common source of bugs.
It also accepts things like class declarations, which will throw at runtime as they will not be called with `new`.
If you are expecting the function to accept certain arguments, you should explicitly define the function shape @typescript-eslint/ban-types
改成 func?:()=>void 就不报这个问题了
收起
点赞
回复
删除
”除了标记一个属性为可选以外,你还可以标记这个属性为只读:readonly。很多同学对这一关键字比较陌生,因为以往 JavaScript 中并没有这一类概念”
js不是可以定义object里的属性是只读的吗?
点赞
回复
删除
谢谢
应该是编辑器带了 ESLint 保存修复,第一种情况类型是可以被推导出来的,有一条规则是不允许推导出的类型和标注类型相同,认为是冗余的
点赞
回复
删除
还有 关于{}空的object
const tmp30: {} = { name: 'linbudu' };
tmp30.age = 18; // X 类型“{}”上不存在属性“age”。

这里确实是报错
但是如果我改成
const tmp30: {name:string,age?:number} = { name: 'linbudu' };
tmp30.age = 18;
就可以成功通过
是否可以理解为这里其实是在声明时定义了一个interface?
收起
点赞
回复
删除
差不多,这种叫字面量类型, 写什么就限制是什么. 额外扩展起来不方便
还有 关于{}空的object
const tmp30: {} = { name: 'linbudu' };
tmp30.age = 18; // X 类型“{}”上不存在属性“age”。

这里确实是报错
但是如果我改成
const tmp30: {name:string,age?:number} = { name: 'linbudu' };
tmp30.age = 18;
就可以成功通过
是否可以理解为这里其实是在声明时定义了一个interface?
点赞
回复
btbrad的头像
删除
搬砖 @ 沪郊某工地
[赞]
点赞
回复
居然巢的头像
删除
前端开发工程师
ts4.7.4关闭了strictNullChecks也不能将null和undefined赋值给Object/object/{}?
点赞
1
删除
可以的吧
点赞
回复
CoderBin的头像
删除
🏆 CoderBin前端空间栈
打卡[奋斗]
点赞
回复
狒狒君14014的头像
删除
前端工程师
写得太好了!!![色]
点赞
回复
很帅很愁人的头像
删除
全村工程师 @ 下王庄村委员会
刚开始看 Object,object {}是懵逼的,因为之前从来没注意过,看第二边就清晰了。建议看这懵逼的大佬,静下心多看几遍
点赞
回复
在摸了在摸了的头像
删除
切图仔
打卡
点赞
回复
午与羽的头像
删除
Talking @ Imagination.
打卡
点赞
回复
guaishou的头像
删除
点赞
回复
Big_Rice的头像
删除
前端工程师 @ 未知
打卡
点赞
回复
河淮秦京南的头像
删除
前端
type 与 interface
有点读不通[可怜]
点赞
3
删除
type test_type = { name: string, age: number }
interface test_interface {
name: string
age: number
}
const obj_test1: test_interface = { name: '', age: 0 }
const obj_test2: test_type = { name: '', age: 0 }
收起
2
回复
删除
试一下就明白了 主要看自己的使用习惯自行选择
1
回复
删除
谢谢
试一下就明白了 主要看自己的使用习惯自行选择
点赞
回复
allblueee的头像
删除
打卡~
点赞
回复
栩栩廿六的头像
删除
前端 @ jddj
例子中有些不自己运行一下,完全不知道哪个会报错
1
1
删除
(作者)
会报错的例子咱们在代码里会有标识,以及上方或下方会有说明,我会再优化一下代码显示~
1
回复
Mxr18的头像
删除
打卡
点赞
回复
CharleeWa的头像
删除
Front-end Engineer @ undefined
第一次知道元组 Tuple 的概念, 还是在学习Python的时候,这是Python 当中和列表 List类似的一种数据类型,不同的是,元组的使用场景是那些已经确定的元素的情况下,一旦创建不可修改里面的元素,也就是只读。TS应该是迁移了这一概念吧!
点赞
回复
你看起来好像很好吃的头像
删除
FE @ 快手
hi,这里在关闭strictNullChecks的情况下undefined和null也可以赋值给{}类型的变量,是不是需要注明一下[思考]
image
点赞
2
删除
(作者)
已经有提到这个~ 我会在这里也注明一下
点赞
回复
删除
嗯嗯,上方确实有提到啦,可以辛苦再补一波,hh
已经有提到这个~ 我会在这里也注明一下
点赞
回复
用户8806885335036的头像
删除
[送心]写的真好
点赞
回复
Reddy5323的头像
删除
null 与 undefined 分别表示“这里有值,但是个空值”和“这里没有值”。
这个反了吧
1
9
删除
(作者)
没有反,undefined其实就是你没有声明的变量和对象属性,当然是没有值的,而null这个值是需要你主动赋的
点赞
回复
删除
stackoverflow.com
你这个明显是后端思路
null = 'value' // ReferenceError
undefined = 'value' // 'value'
很明显了,null是没有声明过的,赋值直接会报ReferenceError,我记得YDKJS上有说过这个问题
没有反,undefined其实就是你没有声明的变量和对象属性,当然是没有值的,而null这个值是需要你主动赋的
1
回复
删除
额,哪来前端思路和后端思路… 你甩的这个链接我看了一下也是这个观点,空的值和没有被声明的值,你要不试着讲一下自己的理解?
stackoverflow.com
你这个明显是后端思路
null = 'value' // ReferenceError
undefined = 'value' // 'value'
很明显了,null是没有声明过的,赋值直接会报ReferenceError,我记得YDKJS上有说过这个问题
1
回复
删除
而且你这个例子的声明和我提到的变量声明也不是一回事,你要不再想想[看]
stackoverflow.com
你这个明显是后端思路
null = 'value' // ReferenceError
undefined = 'value' // 'value'
很明显了,null是没有声明过的,赋值直接会报ReferenceError,我记得YDKJS上有说过这个问题
点赞
回复
删除
undefined是全局对象的一个属性。也就是说,它是全局作用域的一个变量。undefined的最初值就是原始数据类型undefined。



看MDN这句话就知道了
stackoverflow.com
你这个明显是后端思路
null = 'value' // ReferenceError
undefined = 'value' // 'value'
很明显了,null是没有声明过的,赋值直接会报ReferenceError,我记得YDKJS上有说过这个问题
点赞
回复
删除
再加个null的说明

值 null 是一个字面量,不像 undefined,它不是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用。
undefined是全局对象的一个属性。也就是说,它是全局作用域的一个变量。undefined的最初值就是原始数据类型undefined。



看MDN这句话就知道了
点赞
回复
删除
是不是可以总结为:
null 是 没有对象 不应该有值
undefined 表示 缺少值,应该有一个值
再加个null的说明

值 null 是一个字面量,不像 undefined,它不是全局对象的一个属性。null 是表示缺少的标识,指示变量未指向任何对象。把 null 作为尚未创建的对象,也许更好理解。在 API 中,null 常在返回类型应是一个对象,但没有关联的值的地方使用。
点赞
回复
删除
我自己的理解是undefined 是指的没有被赋值,比如只定义未赋值的变量,或者定了行参,但没有给入参值,或者没有return 值的函数返回值
null 是赋值了,但是通常用来表示以后可能是一个对象,但目前还不知道对象是什么,所以给了一个null
是不是可以总结为:
null 是 没有对象 不应该有值
undefined 表示 缺少值,应该有一个值
点赞
回复
删除
或许可以理解为,声明一个对象,默认会 赋值一个undefined
我自己的理解是undefined 是指的没有被赋值,比如只定义未赋值的变量,或者定了行参,但没有给入参值,或者没有return 值的函数返回值
null 是赋值了,但是通常用来表示以后可能是一个对象,但目前还不知道对象是什么,所以给了一个null
点赞
回复
木子烁束岸的头像
删除
点赞
回复
非余的头像
删除
前端开发小菜鸟
提一个建议哈。就是注释可以和对应的代码放一行吗?或者和上方/下方的代码有个隔行以做区分,目前这样的看不出来哪个是对的,哪个是错的。刚开始学TS。不好意思哈~
image
1
2
删除
(作者)
好~我会集中优化下
1
回复
删除
好的。谢谢了
好~我会集中优化下
点赞
回复
tohere的头像
删除
vue3使用ts的时候声明了某个对象的类型,然后必须初始化的时候将所有的属性都初始化一遍才行。。。有没有什么比较好的解决方案,还是将所有属性都设置为可选?
1
8
删除
同问
点赞
回复
删除
同问
点赞
回复
删除
+1
点赞
回复
删除
同问
点赞
回复
删除
蹲个答案
点赞
回复
删除
mark 一下,等答案
1
回复
删除
interface IState {
name: string
age: null
}
const state: IState = {} as IState
是你想要的吗?
点赞
回复
删除
既然声明了必须有的属性,那实现的时候,按理说都必须实现,不知道你们除了断言有其他强制的方法吗?
点赞
回复
丶满天星灬的头像
删除
对象有个点就是 key 为变量的话调用会报错,类型上就需要加上一行 [key: any]: any; 这样才不会报错,类型不一定非要any可以自己指定
1
回复
xu的头像
删除
学习
感觉使用 {} 还不如any,any 还能点引用不报错,😂
点赞
回复
我的女孩来接我了吗的头像
删除
前端工程师
一百个具有 symbol 类型的对象,它们的 symbol 类型指的都是 TypeScript 中的同一个类型。
symbol这里没咋看懂
点赞
1
删除
(作者)
就是说假设一百个 string 类型的变量,它们的类型“string”指的都是同一个 string
1
回复
爱吃鱼的桶哥Z的头像
删除
伪 · 全栈打杂攻城狮
打卡学习~
点赞
回复
慰尘的头像
删除
web前端开发
这种写法该怎么理解,不自己写一遍都看不出哪些是错误哪些是正确的,另外语言表达能力欠佳,太口语化
image
1
2
删除
(作者)
因为前面的几节其实是希望快速地过一遍基础,这个例子里我们在上面有提到 object 代表非原始类型的类型xxxxx,所以下面就直接给报错例子了。口语化这个确实不太好说,因为众口难调,我会试着调整下的~
点赞
回复
删除
装饰 errorLensor 插件,粘贴过来,一目了然
点赞
回复
codermao的头像
删除
web实习 @ 小红书
刚好对object类型有困惑、真好 解决了
1
回复
codermao的头像
删除
web实习 @ 小红书
打卡
点赞
回复
馍馍汉宝的头像
删除
打卡[呲牙]
1
回复
The action has been successful