06-探秘内置类型:any、unknown、never 与类型断言
课程
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.

此前我们学习基础类型标注、字面量类型与枚举、函数与 Class 等概念时,实际上一直在用 JavaScript 的概念来进行映射,或者说这可以看作是 JavaScript 代码到 TypeScript 代码的第一步迁移。而这一节,我们要学习的则是,如何使用 TypeScript 提供的内置类型在类型世界里获得更好的编程体验

首先是内置的可用于标注的类型,包括 any、unknown 与 never,加上这一部分我们就掌握了 TypeScript 中所有的内置类型标注。然后是类型断言这一重要能力,我们会介绍它的正确使用场景、双重断言与非空断言等,以及类型断言的幕后原理——类型层级。

这一节是全新的知识,JavaScript 中基本没有类似的概念。但你也不用担心,我们会一步一步从 0 开始学习,让你顺利进入类型编程的世界。

本节代码见:Any & Unknown & Never

内置类型:any 、unknown 与 never

有些时候,我们的 TS 代码并不需要十分精确严格的类型标注。比如 console.log 方法就能够接受任意类型的参数,不管你是数组、字符串、对象或是其他的,统统来者不拒。那么,我们难道要把所有类型用联合类型串起来?

这当然不现实,为了能够表示“任意类型”,TypeScript 中提供了一个内置类型 any ,来表示所谓的任意类型。此时我们就可以使用 any 作为参数的类型:

log(message?: any, ...optionalParams: any[]): void

在这里,一个被标记为 any 类型的参数可以接受任意类型的值。除了 message 是 any 以外,optionalParams 作为一个 rest 参数,也使用 any[] 进行了标记,这就意味着你可以使用任意类型的任意数量类型来调用这个方法。除了显式的标记一个变量或参数为 any,在某些情况下你的变量/参数也会被隐式地推导为 any。比如使用 let 声明一个变量但不提供初始值,以及不为函数参数提供类型标注:

// any
let foo;

// foo、bar 均为 any
function func(foo, bar){}

以上的函数声明在 tsconfig 中启用了 noImplicitAny 时会报错,你可以显式为这两个参数指定 any 类型,或者暂时关闭这一配置(不推荐)。而 any 类型的变量几乎无所不能,它可以在声明后再次接受任意类型的值,同时可以被赋值给任意其它类型的变量:

// 被标记为 any 类型的变量可以拥有任意类型的值
let anyVar: any = "linbudu";

anyVar = false;
anyVar = "linbudu";
anyVar = {
  site: "juejin"
};

anyVar = () => { }

// 标记为具体类型的变量也可以接受任何 any 类型的值
const val1: string = anyVar;
const val2: number = anyVar;
const val3: () => {} = anyVar;
const val4: {} = anyVar;

你可以在 any 类型变量上任意地进行操作,包括赋值、访问、方法调用等等,此时可以认为类型推导与检查是被完全禁用的:

let anyVar: any = null;

anyVar.foo.bar.baz();
anyVar[0][1][2].prop1;

而 any 类型的主要意义,其实就是为了表示一个无拘无束的“任意类型”,它能兼容所有类型,也能够被所有类型兼容。这一作用其实也意味着类型世界给你开了一个外挂,无论什么时候,你都可以使用 any 类型跳过类型检查。当然,运行时出了问题就需要你自己负责了。

any 的本质是类型系统中的顶级类型,即 Top Type,这是许多类型语言中的重要概念,我们会在类型层级部分讲解。

any 类型的万能性也导致我们经常滥用它,比如类型不兼容了就 any 一下,类型不想写了也 any 一下,不确定可能会是啥类型还是 any 一下。此时的 TypeScript 就变成了令人诟病的 AnyScript。为了避免这一情况,我们要记住以下使用小 tips :

  • 如果是类型不兼容报错导致你使用 any,考虑用类型断言替代,我们下面就会开始介绍类型断言的作用。
  • 如果是类型太复杂导致你不想全部声明而使用 any,考虑将这一处的类型去断言为你需要的最简类型。如你需要调用 foo.bar.baz(),就可以先将 foo 断言为一个具有 bar 方法的类型。
  • 如果你是想表达一个未知类型,更合理的方式是使用 unknown。

unknown 类型和 any 类型有些类似,一个 unknown 类型的变量可以再次赋值为任意其它类型,但只能赋值给 any 与 unknown 类型的变量:

let unknownVar: unknown = "linbudu";

unknownVar = false;
unknownVar = "linbudu";
unknownVar = {
  site: "juejin"
};

unknownVar = () => { }

const val1: string = unknownVar; // Error
const val2: number = unknownVar; // Error
const val3: () => {} = unknownVar; // Error
const val4: {} = unknownVar; // Error

const val5: any = unknownVar;
const val6: unknown = unknownVar;

unknown 和 any 的一个主要差异体现在赋值给别的变量时,any 就像是 “我身化万千无处不在” ,所有类型都把它当自己人。而 unknown 就像是 “我虽然身化万千,但我坚信我在未来的某一刻会得到一个确定的类型” ,只有 any 和 unknown 自己把它当自己人。简单地说,any 放弃了所有的类型检查,而 unknown 并没有。这一点也体现在对 unknown 类型的变量进行属性访问时:

let unknownVar: unknown;

unknownVar.foo(); // 报错:对象类型为 unknown

要对 unknown 类型进行属性访问,需要进行类型断言(别急,马上就讲类型断言!),即“虽然这是一个未知的类型,但我跟你保证它在这里就是这个类型!”:

let unknownVar: unknown;

(unknownVar as { foo: () => {} }).foo();

在类型未知的情况下,更推荐使用 unknown 标注。这相当于你使用额外的心智负担保证了类型在各处的结构,后续重构为具体类型时也可以获得最初始的类型信息,同时还保证了类型检查的存在。当然,unknown 用起来很麻烦,一堆类型断言写起来可不太好看。归根结底,到底用哪个完全取决于你自己,毕竟语言只是工具嘛。

如果说,any 与 unknown 是比原始类型、对象类型等更广泛的类型,也就是说它们更上层一些,就像 string 字符串类型比 'linbudu' 字符串字面量更上层一些,即 any/unknown -> 原始类型、对象类型 -> 字面量类型。那么,是否存在比字面量类型更底层一些的类型

这里的上层与底层,其实即意味着包含类型信息的多少。any 类型包括了任意的类型,字符串类型包括任意的字符串字面量类型,而字面量类型只表示一个精确的值类型。如要还要更底层,也就是再少一些类型信息,那就只能什么都没有了。

而内置类型 never 就是这么一个“什么都没有”的类型。此前我们已经了解了另一个“什么都没有”的类型,void。但相比于 void ,never 还要更加空白一些。

虚无的 never 类型

是不是有点不好理解?我们看一个联合类型的例子就能 get 到一些了。

type UnionWithNever = "linbudu" | 599 | true | void | never;

将鼠标悬浮在类型别名之上,你会发现这里显示的类型是"linbudu" | 599 | true | void。never 类型被直接无视掉了,而 void 仍然存在。这是因为,void 作为类型表示一个空类型,就像没有返回值的函数使用 void 来作为返回值类型标注一样,void 类型就像 JavaScript 中的 null 一样代表“这里有类型,但是个空类型”。

而 never 才是一个“什么都没有”的类型,它甚至不包括空的类型,严格来说,never 类型不携带任何的类型信息,因此会在联合类型中被直接移除,比如我们看 void 和 never 的类型兼容性:

declare let v1: never;
declare let v2: void;

v1 = v2; // X 类型 void 不能赋值给类型 never

v2 = v1;

在编程语言的类型系统中,never 类型被称为 Bottom Type,是整个类型系统层级中最底层的类型。和 null、undefined 一样,它是所有类型的子类型,但只有 never 类型的变量能够赋值给另一个 never 类型变量。

通常我们不会显式地声明一个 never 类型,它主要被类型检查所使用。但在某些情况下使用 never 确实是符合逻辑的,比如一个只负责抛出错误的函数:

function justThrow(): never {
  throw new Error()
}

在类型流的分析中,一旦一个返回值类型为 never 的函数被调用,那么下方的代码都会被视为无效的代码(即无法执行到):

function justThrow(): never {
  throw new Error()
}

function foo (input:number){
  if(input > 1){
    justThrow();
    // 等同于 return 语句后的代码,即 Dead Code
    const name = "linbudu";
  }
}

我们也可以显式利用它来进行类型检查,即上面在联合类型中 never 类型神秘消失的原因。假设,我们需要对一个联合类型的每个类型分支进行不同处理:

declare const strOrNumOrBool: string | number | boolean;

if (typeof strOrNumOrBool === "string") {
  console.log("str!");
} else if (typeof strOrNumOrBool === "number") {
  console.log("num!");
} else if (typeof strOrNumOrBool === "boolean") {
  console.log("bool!");
} else {
  throw new Error(`Unknown input type: ${strOrNumOrBool}`);
}

如果我们希望这个变量的每一种类型都需要得到妥善处理,在最后可以抛出一个错误,但这是运行时才会生效的措施,是否能在类型检查时就分析出来?

实际上,由于 TypeScript 强大的类型分析能力,每经过一个 if 语句处理,strOrNumOrBool 的类型分支就会减少一个(因为已经被对应的 typeof 处理过)。而在最后的 else 代码块中,它的类型只剩下了 never 类型,即一个无法再细分、本质上并不存在的虚空类型。在这里,我们可以利用只有 never 类型能赋值给 never 类型这一点,来巧妙地分支处理检查:

if (typeof strOrNumOrBool === "string") {
    // 一定是字符串!
  strOrNumOrBool.charAt(1);
} else if (typeof strOrNumOrBool === "number") {
  strOrNumOrBool.toFixed();
} else if (typeof strOrNumOrBool === "boolean") {
  strOrNumOrBool === true;
} else {
  const _exhaustiveCheck: never = strOrNumOrBool;
  throw new Error(`Unknown input type: ${_exhaustiveCheck}`);
}

假设某个粗心的同事新增了一个类型分支,strOrNumOrBool 变成了 strOrNumOrBoolOrFunc,却忘记新增对应的处理分支,此时在 else 代码块中就会出现将 Function 类型赋值给 never 类型变量的类型错误。这实际上就是利用了类型分析能力与 never 类型只能赋值给 never 类型这一点,来确保联合类型变量被妥善处理。

前面我们提到了主动使用 never 类型的两种方式,而 never 其实还会在某些情况下不请自来。比如说,你可能遇到过这样的类型错误:

const arr = [];

arr.push("linbudu"); // 类型“string”的参数不能赋给类型“never”的参数。

此时这个未标明类型的数组被推导为了 never[] 类型,这种情况仅会在你启用了 strictNullChecks 配置,同时禁用了 noImplicitAny 配置时才会出现。解决的办法也很简单,为这个数组声明一个具体类型即可。关于这两个配置的具体作用,我们会在后面有详细的介绍。

在这一部分,我们了解了 TypeScript 中 Top Type(any / unknown) 与 Bottom Type(never)它们的表现。在讲 any 的时候,我们在小 tips 中提到,可以使用类型断言来避免对 any 类型的滥用。那么接下来,我们就来学习类型断言这一概念。

类型断言:警告编译器不准报错

类型断言能够显式告知类型检查程序当前这个变量的类型,可以进行类型分析地修正、类型。它其实就是一个将变量的已有类型更改为新指定类型的操作,它的基本语法是 as NewType,你可以将 any / unknown 类型断言到一个具体的类型:

let unknownVar: unknown;

(unknownVar as { foo: () => {} }).foo();

还可以 as 到 any 来为所欲为,跳过所有的类型检查:

const str: string = "linbudu";

(str as any).func().foo().prop;

也可以在联合类型中断言一个具体的分支:

function foo(union: string | number) {
  if ((union as string).includes("linbudu")) { }

  if ((union as number).toFixed() === '599') { }
}

但是类型断言的正确使用方式是,在 TypeScript 类型分析不正确或不符合预期时,将其断言为此处的正确类型:

interface IFoo {
  name: string;
}

declare const obj: {
  foo: IFoo
}

const {
  foo = {} as IFoo
} = obj

这里从 {} 字面量类型断言为了 IFoo 类型,即为解构赋值默认值进行了预期的类型断言。当然,更严谨的方式应该是定义为 Partial<IFoo> 类型,即 IFoo 的属性均为可选的。

除了使用 as 语法以外,你也可以使用 <> 语法。它虽然书写更简洁,但效果一致,只是在 TSX 中尖括号断言并不能很好地被分析出来。你也可以通过 TypeScript ESLint 提供的 consistent-type-assertions 规则来约束断言风格。

需要注意的是,类型断言应当是在迫不得己的情况下使用的。虽然说我们可以用类型断言纠正不正确的类型分析,但类型分析在大部分场景下还是可以智能地满足我们需求的。

总的来说,在实际场景中,还是 as any 这一种操作更多。但这也是让你的代码编程 AnyScript 的罪魁祸首之一,请务必小心使用。

双重断言

如果在使用类型断言时,原类型与断言类型之间差异过大,也就是指鹿为马太过离谱,离谱到了指鹿为霸王龙的程度,TypeScript 会给你一个类型报错:

const str: string = "linbudu";

// 从 X 类型 到 Y 类型的断言可能是错误的,blabla
(str as { handler: () => {} }).handler()

此时它会提醒你先断言到 unknown 类型,再断言到预期类型,就像这样:

const str: string = "linbudu";

(str as unknown as { handler: () => {} }).handler();

// 使用尖括号断言
(<{ handler: () => {} }>(<unknown>str)).handler();

这是因为你的断言类型和原类型的差异太大,需要先断言到一个通用的类,即 any / unknown。这一通用类型包含了所有可能的类型,因此断言到它从它断言到另一个类型差异不大。

非空断言

非空断言其实是类型断言的简化,它使用 ! 语法,即 obj!.func()!.prop 的形式标记前面的一个声明一定是非空的(实际上就是剔除了 null 和 undefined 类型),比如这个例子:

declare const foo: {
  func?: () => ({
    prop?: number | null;
  })
};

foo.func().prop.toFixed();

此时,func 在 foo 中不一定存在,prop 在 func 调用结果中不一定存在,且可能为 null,我们就会收获两个类型报错。如果不管三七二十一地坚持调用,想要解决掉类型报错就可以使用非空断言:

foo.func!().prop!.toFixed();

其应用位置类似于可选链:

foo.func?.().prop?.toFixed();

但不同的是,非空断言的运行时仍然会保持调用链,因此在运行时可能会报错。而可选链则会在某一个部分收到 undefined 或 null 时直接短路掉,不会再发生后面的调用。

非空断言的常见场景还有 document.querySelectorArray.find 方法等:

const element = document.querySelector("#id")!;
const target = [1, 2, 3, 599].find(item => item === 599)!;

为什么说非空断言是类型断言的简写?因为上面的非空断言实际上等价于以下的类型断言操作:

((foo.func as () => ({
  prop?: number;
}))().prop as number).toFixed();

怎么样,非空断言是不是简单多了?你可以通过 non-nullable-type-assertion-style 规则来检查代码中是否存在类型断言能够被简写为非空断言的情况。

类型断言还有一种用法是作为代码提示的辅助工具,比如对于以下这个稍微复杂的接口:

interface IStruct {
  foo: string;
  bar: {
    barPropA: string;
    barPropB: number;
    barMethod: () => void;
    baz: {
      handler: () => Promise<void>;
    };
  };
}

假设你想要基于这个结构随便实现一个对象,你可能会使用类型标注:

const obj: IStruct = {};

这个时候等待你的是一堆类型报错,你必须规规矩矩地实现整个接口结构才可以。但如果使用类型断言,我们可以在保留类型提示的前提下,不那么完整地实现这个结构:

// 这个例子是不会报错的
const obj = <IStruct>{
  bar: {
    baz: {},
  },
};

类型提示仍然存在:

在你错误地实现结构时仍然可以给到你报错信息:

总结与预告

在这一节中,我们学习了 TypeScript 中的内置类型 any、unknown 与 never,包括它们的类型兼容性表现与使用场景。而在另外一部分类型断言中,我们了解了类型断言的基本使用,以及结合内置类型 any 的使用场景。

在下一节,我们会开始对 TypeScript 类型工具的学习,进一步探索 TypeScript 的类型世界,包括类型别名、交叉类型、索引类型、映射类型等等。如果说基础类型是积木,那这些类型工具就是积木工厂?它们在基础类型的基础之上进行类型编程运算如组合、过滤等,得到更贴近你实际需要形状的积木,也带你认识到,原来不止可以对变量进行编程,类型也可以!

扩展阅读

类型层级初探

这一节的知识点其实都和 TypeScript 的类型层级有所关联,我们会在后面的类型系统部分有专门一节进行详细地讲述,这里只做简单地描述来供有兴趣的同学提前了解。

前面我们已经说到,any 与 unknown 属于 Top Type,表现在它们包含了所有可能的类型,而 never 属于 Bottom Type,表现在它是一个虚无的、不存在的类型。那么加上此前学习的原始类型与字面量类型等,按照类型的包含来进行划分,我们大概能梳理出这么个类型层级关系。

  • 最顶级的类型,any 与 unknown
  • 特殊的 Object ,它也包含了所有的类型,但和 Top Type 比还是差了一层
  • String、Boolean、Number 这些装箱类型
  • 原始类型与对象类型
  • 字面量类型,即更精确的原始类型与对象类型嘛,需要注意的是 null 和 undefined 并不是字面量类型的子类型
  • 最底层的 never

实际上这个层级链并不完全,因为还有联合类型、交叉类型、函数类型的情况,我们会在后面专门有一节进行讲解~

而实际上类型断言的工作原理也和类型层级有关,在判断断言是否成立,即差异是否能接受时,实际上判断的即是这两个类型是否能够找到一个公共的父类型。比如 { }{ name: string } 其实可以认为拥有公共的父类型 {}(一个新的 {}!你可以理解为这是一个基类,参与断言的 { }{ name: string } 其实是它的派生类)。

如果找不到具有意义的公共父类型呢?这个时候就需要请出 Top Type 了,如果我们把它先断言到 Top Type,那么就拥有了公共父类型 Top Type,再断言到具体的类型也是同理。你可以理解为先向上断言,再向下断言,比如前面的双重断言可以改写成这样:

const str: string = "linbudu";

(str as (string | { handler: () => {} }) as { handler: () => {} }).handler();

这一部分的扩展阅读只是为了让你提前意识到类型层级的存在,并不需要完全理解,毕竟我们后面还有一整节会讲类型系统层级呢。

留言
Ctrl + Enter
全部评论(67)
phy_lei的头像
删除
api调用前端工程师 @ 量子股份有限公司
```ts
const obj = <IStruct>{
bar: {
baz: {},
},
};
```
例子中这里我怎么报错了,请问大佬,这是为啥?
展开
image
点赞
回复
Geek喜多川的头像
删除
面向工资💰编程 @ 人生不设限公司
脑图
image
点赞
回复
fendiman的头像
删除
打卡
点赞
回复
小喵呜的头像
删除
嚎哭深渊典狱长
打卡
点赞
回复
小阿罗的头像
删除
前端工程师 @ febd
```此时这个未标明类型的数组被推导为了 never[] 类型,这种情况仅会在你启用了 strictNullChecks 配置,同时禁用了 noImplicitAny 配置时才会出现。解决的办法也很简单,为这个数组声明一个具体类型即可```
大佬好,为什么加了这俩配置,仍然没报错呢
点赞
1
删除
(作者)
给具体playground哈
点赞
回复
嘎嘎🙊的头像
删除
打卡[力量]
点赞
回复
momomao的头像
删除
前端研发
打卡
点赞
回复
Zicxxciz的头像
删除
前端工程师 @ ****
打卡
点赞
回复
yyhh的头像
删除
臭鱼烂虾
const obj = <IStruct>{
bar: {
baz: {},
},
};
这个例子牛逼啊,
点赞
回复
东东吖的头像
删除
前端工程师
1
1
回复
机智的狮老师的头像
删除
可视化工程师
打卡
点赞
回复
用户2362677620139的头像
删除
打卡
点赞
回复
大家好我叫卢同学的头像
删除
学生仔 @ GUET
打卡 Day2
点赞
回复
多吃牛肉减肥的头像
删除
可以
点赞
回复
行走的仆累的头像
删除
前端工程师 @ 顺丰科技有限公司
打卡
点赞
回复
寿司八哥的头像
删除
Web3D @ 图形起源
打卡
点赞
回复
btbrad的头像
删除
搬砖 @ 沪郊某工地
[赞]
点赞
回复
前端摸鱼儿的头像
删除
第二遍打卡
点赞
回复
涛涛_江的头像
删除
打卡
点赞
回复
配角七三的头像
删除
我觉得把“never 类型只能赋值给 never 类型”这个表述改成“只有never 类型能赋值给 never 类型”会好点
7
2
删除
我认为合理,我是作者我就改去了
点赞
回复
删除
听君一席话,胜听一席话啊
点赞
回复
歷史總是驚人的相似的头像
删除
打卡
点赞
回复
前端摸鱼儿的头像
删除
这节不错, 开发中遇到的never类型推到 和 对象类型断言都讲了
点赞
回复
阳树阳树的头像
删除
蔚来@FE前端
打卡
点赞
回复
很帅很愁人的头像
删除
全村工程师 @ 下王庄村委员会
打卡
点赞
回复
guaishou的头像
删除
点赞
回复
Geek喜多川的头像
删除
面向工资💰编程 @ 人生不设限公司
打卡
点赞
回复
Big_Rice的头像
删除
前端工程师 @ 未知
打卡
点赞
回复
勇敢的小白兔的头像
删除
致作者:在类型未知的情况下,更推荐使用 unknown 标注。这相当于你使用额外的心智负担保证了类型在各处的结构,后续重构为具体类型时也可以获得最初始的类型信息,同时还保证了类型检查的存在。

请问怎么理解"后续重构为具体类型时也可获取最初始的类型信息"这句话,
我的理解是:最开始不知道具体类型的时候,可以用unknown进行注解,但是后续重构为具体类型的时候,此时还能获取到unknown类型??
收起
点赞
1
删除
(作者)
unknown类型的变量,假设你调用它的方法,需要按照你的需要把它断言到一个具有此方法的类型(不需要完全描述),初始的类型信息指的就是这个
点赞
回复
勇敢的小白兔的头像
删除
非空断言把握住了,类型断言水太深把握不住,还得再思考下
1
回复
exec的头像
删除
web前端开发
type s = { key: '123', value: 123 } extends { key: string } ? true : false // true
let d: { key: string } = { key: '123', value: 123 } // 报错

{ key: '123', value: 123 } 是 { key: string } 的子类,按道理来说应该也可以直接赋值,而且不是都提倡面向接口么,为什么 typescirpt 反而要限制这样呢?反而带来实际上编码不方便。
1
4
删除
(作者)
这是因为你直接使用: { key: string } 的标注的时候,其实是在使用「对象类型的字面量类型」,就像字符串的字面量类型"linbudu"只能接受字符串"linbudu"一样,对象类型字面量也需要值完全符合它的结构,你需要把这个类型作为一个类型别名或者接口,才能享受到 type s 中的基于结构化类型的比较,也就是可以新增属性
1
回复
删除
interface D { key: string }
let d: D = { key: '123', value: 123 } // 报错

也是不行。好吧,这个问题属实我转牛角尖了,我觉得这里应该是 ts 限制,字面量不允许隐式向上转换
这是因为你直接使用: { key: string } 的标注的时候,其实是在使用「对象类型的字面量类型」,就像字符串的字面量类型"linbudu"只能接受字符串"linbudu"一样,对象类型字面量也需要值完全符合它的结构,你需要把这个类型作为一个类型别名或者接口,才能享受到 type s 中的基于结构化类型的比较,也就是可以新增属性
点赞
回复
删除
我觉得吧,ts应该是把 {key,value}推断成{key:string,value:number} 然后你在赋值给d, 但是那D的类型只有一个key 所以报错了。

interface D { key: string }
let d: D & {value:number} = { key: '123', value: 123 }
或者可以 断言
let d = <D>{ key: '123', value: 123 }
收起
interface D { key: string }
let d: D = { key: '123', value: 123 } // 报错

也是不行。好吧,这个问题属实我转牛角尖了,我觉得这里应该是 ts 限制,字面量不允许隐式向上转换
点赞
回复
删除
时间有点久,给忘了,ts有个配置好像可以控制,当时我觉得应该是ts特殊限制了
我觉得吧,ts应该是把 {key,value}推断成{key:string,value:number} 然后你在赋值给d, 但是那D的类型只有一个key 所以报错了。

interface D { key: string }
let d: D & {value:number} = { key: '123', value: 123 }
或者可以 断言
let d = <D>{ key: '123', value: 123 }
点赞
回复
爱吃鱼的桶哥Z的头像
删除
伪 · 全栈打杂攻城狮
typescript写多了真的就写成了anyscript了[哭笑]
2
回复
furfurJiang的头像
删除
前端 @ 程序江
> 非空断言的运行时仍然会保持调用链,因此在运行时可能会报错。而可选链则会在某一个部分收到 undefined 或 null 时直接短路掉,不会再发生后面的调用。

这样看起来不是可选链实用得多吗?为什么还要非空断言调用链?[疑问]
1
3
删除
(作者)
如果你能保证这里一定是有值的,就可以用非空断言呀~ 当然实际层面推荐只要不是完全能保障(比如有默认值),就用可选链来进行调用
2
回复
删除
能保证这里一定有值,那就不用写可选类型吧,不写可选类型,不就没有类型问题了
如果你能保证这里一定是有值的,就可以用非空断言呀~ 当然实际层面推荐只要不是完全能保障(比如有默认值),就用可选链来进行调用
点赞
回复
删除
一个可选类型可能被多个对象实现,在某处可能可以保证有值,但不一定在每一处
能保证这里一定有值,那就不用写可选类型吧,不写可选类型,不就没有类型问题了
点赞
回复
furfurJiang的头像
删除
前端 @ 程序江
大佬好,这个做法不管传入字符串还是传入数字,不都会报错吗[疑问]
image
点赞
6
删除
(作者)
是的,因为有联合类型
1
回复
删除
可以我这里测试是会报错的呀
image
是的,因为有联合类型
点赞
回复
删除
这个报错是预期的呀,前面说的不会报错是指类型层面不会报错,你这里报错是因为字符串本身就没有 toFixed 方法
可以我这里测试是会报错的呀
点赞
回复
删除
get,理解错意思了
这个报错是预期的呀,前面说的不会报错是指类型层面不会报错,你这里报错是因为字符串本身就没有 toFixed 方法
点赞
回复
删除
这个写法,只能是绕开了类型检查吧,实际上无论怎么传值,都是会报错的啊
点赞
回复
删除
你的断言告诉计算机 你知道他们是什么类型,但是对于判断并没有意义,始终取得还是联合类型中的第一个string,用typeof
点赞
回复
余小C的头像
删除
你好,如下例子中 interface Userinfo {
name: string;id:string,avater:string }
export const userInfoRecoilState = atom<Userinfo>({ key: 'userInfoRecoilState', default: { id: '' } }); 这种方式如何设置一个默认值,现在Userinfo接口只有三个属性,假设有20个属性时如何设置默认值?请大佬赐教
点赞
回复
y494890512的头像
删除
type IsNever<T> = [T] extends [never] ? true : false; 判断是否是never 为啥要用 [never] 判断
点赞
4
删除
使用[]包裹,联合类型的extends不会进行分发。
点赞
回复
删除
大佬,能详细说说吗,没见上文中有介绍呀
使用[]包裹,联合类型的extends不会进行分发。
点赞
回复
删除
其实记住就行了,具体的原理我也不知道了。反正避免联合类型进行extends时分发,用[]包裹就行了
大佬,能详细说说吗,没见上文中有介绍呀
点赞
回复
删除
我搜到相对应的答案了,never属于联合类型,会触发分发的,避免触发分发的可以用数组或者元组或者promise包裹
其实记住就行了,具体的原理我也不知道了。反正避免联合类型进行extends时分发,用[]包裹就行了
1
回复
Ycan109690的头像
删除
文中“noImplictAny” 拼写错了,应为“noImplicitAny”
点赞
1
删除
(作者)
已更新~
1
回复
树洞的头像
删除
自动匿名机器人 @ 自动匿名机器人
if (typeof strOrNumOrBool === "string") {
// 一定是字符串!
strOrNumOrBool.charAt(1);
} else if (typeof strOrNumOrBool === "number") {
strOrNumOrBool.toFixed();
} else if (typeof strOrNumOrBool === "boolean") {
strOrNumOrBool === true;
} else {
const _exhaustiveCheck: never = strOrNumOrBool;
throw new Error(`Unknown input type: ${_exhaustiveCheck}`);
}

这个与 lint 冲突了怎么办
展开
点赞
1
删除
(作者)
看你是想要这种代码+类型保障还是lint保障了,一般来说推荐使用前者,比如在这里禁用掉那条规则
点赞
回复
tohere的头像
删除
any 类型的万能性也导致我们经常滥用它,比如类型不兼容了就 any 一下,类型不想写了也 any 一下,不确定可能会是啥类型还是 any 一下。这是我的真实写照[尬笑]
4
2
删除
我也是。。。哈哈哈
点赞
回复
删除
不知道啥类型用unkown比较好,之后再用类型断言。any平时也用,用得少
点赞
回复
codermao的头像
删除
web实习 @ 小红书
他化自在法,作者是不是看过完美世界 哈哈[不失礼貌的微笑]
2
回复
馍馍汉宝的头像
删除
哈哈,类型断言作为代码提示的辅助工具,太秀了[泣不成声]
4
2
删除
我也是第一次知道这个。哈哈,之前硬是把不需要的属性也定义了,可愁死我了
1
回复
删除
确实,我今天才看到断言的好处
我也是第一次知道这个。哈哈,之前硬是把不需要的属性也定义了,可愁死我了
点赞
回复
The action has been successful