盘点 TypeScript 中的易混同点

本文所有示例均可在 Playground 验证 ????

原文:https://github.com/gauseen/bl...

any VS unknown VS void VS never

any

any 用来示意能够赋值为任意类型,包含 any 类型值的属性和办法,所有类型都能被赋值给它,它也能被赋值给其余任何类型,在 TypeScript 中尽量避免应用

let anyThing: any = 'hello'// 以下在编译时不会报错,在运行时报错,失去了 TypeScript 类型查看的意义console.log(anyThing.todo())console.log(anyThing.todo().abc)

unknown

unknownany 类型对应的平安类型,在对 unknown 类型的值执行大多数操作之前,咱们必须进行某种模式的查看。而在对 any 类型的值执行操作之前,咱们不用进行任何查看

let value: unknownvalue = undefined // okvalue = null // okvalue = true // okvalue = 86 // okvalue = 'hello' // okvalue = {} // okvalue = Symbol() // ok

unknown 类型大多数操作都认为是谬误的

let value: unknownlet v1: unknown = value // oklet v2: any = value // oklet v3: undefined = value // Errorlet v4: null = value // Errorlet v5: string = value // Errorlet v6: number = value // Errorlet v7: boolean = value // Errorlet v8: symbol = value // Errorconsole.log(value.key) // Errorvalue.foo() // Error

所以在操作 unknown 类型前,应该放大类型范畴,能够通过:typeof、instanceof、as、is

let value: unknown = 'hello'// 通过 typeof 放大类型范畴if (typeof value === 'string') {  console.log(value.length)}

void

void 示意没有任何类型,只能将它赋值为 undefinednull

// 用 void 示意没有任何返回值的函数function alertFunc(): void {  alert('gauseen')}

never

never 示意永远不存在的值的类型, never 类型只能赋值给另外一个 never

// 一个从来不会有返回值的函数function foo(): never {  while (true) {}  alert('执行?')}// 一个总是会抛出谬误的函数function foo(): never {  throw new Error('some error')}

当一个函数没有返回值时,它返回了一个 void 类型,然而,当一个函数基本就没有返回值时(陷入死循环或者总是抛出谬误),它返回一个 nevervoid 指能够被赋值的类型(在 strictNullChecking 为 false 时),其余任何类型不能赋值给 never,除了 never 自身以外

interface VS type

开发中,常常用 interfacetype 用于类型申明,对与老手来说非常容易混同,上面梳理一下它们之间的相同点与不同点。

相同点

定义对象或函数

两者都能够定义对象或者函数,然而语法有所不同,示例如下:

// interfaceinterface Point {  x: number  y: number}interface SetPoint {  (x: number, y: number): void}
// typetype Point = {  x: number  y: number}type SetPoint = (x: number, y: number) => void

Extend(继承)

两者都能够 extends(继承),但语法不同。包含,interface 能够继承 typetype 也能够继承 interface

⚠️ interface 不能够继承 type 的联结类型

// interface extends interfaceinterface PointX {  x: number}interface Point extends PointX {  y: number}// type alias extends type aliastype PointX = { x: number }type Point = PointX & { y: number }// interface extends type aliastype PointX = { x: number }interface Point extends PointX {  y: number}// Error: interface 无奈继承联结类型type PointX = { x: number } | { y: number }interface Point extends PointX {  y: number}// type alias extends interfaceinterface PointX {  x: number}type Point = PointX & { y: number }

Implements(实现)

一个 class 能够实现 interface 或 type

⚠️ class 不能实现 type 的联结类型

interface Point {  x: number  y: number}class SomePoint implements Point {  x = 1  y = 2}type Point2 = {  x: number  y: number}class SomePoint2 implements Point2 {  x = 1  y = 2}type Point3 = { x: number } | { y: number }// Error: 无奈实现联结类型class SomePoint3 implements Point3 {  x = 1  y = 2}

不同点

type 用于其余类型的别名

type 也能够用于其余类型,如:根本类型、联结类型、元组,但 interface 不能够

// 根底类型的别名type Name = string// 联结类型(union)type Age = string | number// 元组type Data = [number, string]

申明合并

一个 interface 能够定义屡次,并将做为单个接口(所有申明的成员都将被合并),但 type 不能够同名屡次申明

interface Point {  x: number}interface Point {  y: number}const point: Point = { x: 1, y: 2 }

怎么应用?

对于如何应用,其实这是个仁者见仁智者见智的问题,上面说一下我的认识
  • 前置条件,应用时首先应该与团队已有标准保持一致
  • 平时应用时,倡议尽量应用 interface 来代替 type,官网文档也有所阐明
  • 无奈通过 interface 来定义一个类型时,抉择应用 type,例如形容,根底类型的别名、联结类型、元组

is VS as

is

TypeScript 中 is 关键字示意是否属于某个类型,能够无效地放大类型范畴

如下代码,封装一个 isString 函数,来判断某个值是否为 string 类型,函数返回值为 boolean 类型。

function isString(val: any): boolean {  return typeof val === 'string'}function example(foo: any) {  if (isString(foo)) {    console.log('a string' + foo)    console.log(foo.length)    console.log(foo.toSome(2)) // 编译不报错,在运行时报错,foo 没有 toSome 办法  }}

下面的代码编译阶段不报错,但在运行时会报错,然而为什么 TS 类型检测没有报错呢?默认这种状况下 TypeScript 不会放大块作用域中的类型,此时 TypeScript 认为 fooany 类型,所以 foo.toSome(2) 不会呈现编译谬误,但 foo.toSome() 办法的确不存在,所以会呈现运行时谬误。那么如何防止这种状况产生呢?—— 应用 is 关键字,具体示例如下:

function isString(val: any): val is string {  return typeof val === 'string'}function example(foo: any) {  if (isString(foo)) {    console.log('it is a string' + foo)    console.log(foo.length)    console.log(foo.toSome(2)) // 编译时报错,运行时报错  }}

应用 val is string 函数返回类型,而不是将 boolean 用为函数返回类型。因为在调用 isString() 之后,如果函数返回 true,TypeScript 会将类型范畴放大为 string,在编译时就能发现代码谬误。

is 关键字能够无效的放大类型范畴,能够帮忙咱们在编辑阶段发现错误,从而防止一些暗藏的运行时谬误,这也是 TypeScript 的劣势所在。

as

TypeScript 容许手动笼罩它的推断,能够手动指定某个值的类型,这种机制被称为「类型断言」。

可断言的状况:

  • 联结类型能够被断言为其中一个类型
  • 父类能够被断言为子类
  • 任何类型都能够被断言为 any
  • any 能够被断言为任何类型
  • 要使得 A 可能被断言为 B,只须要 A 兼容 BB 兼容 A 即可
interface IPerson {  age: number  name: string  weight: string}const gauseen: IPerson = {  age: 26,  name: 'gauseen',  weight: '600kg',}// Error// 因为 `IPerson` 类型的索引值只有 `"age" | "name" | "weight"`,所以 `string` 类型不能作为 `IPerson` 类型的索引function getValue(obj: IPerson, key: string) {  return obj[key]}getValue(gauseen, 'age')// 注⚠️:只是做演示应用,不举荐这样断言function getValue(obj: IPerson, key: string) {  return obj[key as keyof IPerson]}getValue(gauseen, 'age')// OKfunction getValue(obj: IPerson, key: keyof IPerson) {  return obj[key]}getValue(gauseen, 'age')

keyof

keyofinterface 的键,返回值可作为一个联结类型

interface IPerson {  age: number  name: string  weight: string}const gauseen: IPerson = {  age: 26,  name: 'gauseen',  weight: '65kg',}// ErrorObject.keys(gauseen).map((key) => gauseen[key])// Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'IPerson'.// No index signature with a parameter of type 'string' was found on type 'IPerson'.(7053)

为什么第一个会报错?
因为 Object.keys() 返回 string[],string 类型值不能作为 gauseen 对象的索引,因为取值有可能会返回 undefined

如何解决?
应该给 Object.keys(gauseen) 返回值做个束缚/断言,让 ts 晓得它的返回的值属于 gauseen 对象中的某个键,如下:

// Passtype Keys = 'age' | 'name' | 'weight';(Object.keys(gauseen) as Array<Keys>).map((key) => gauseen[key])

通过 keyof 更优雅的管制,实际效果跟下面一样

// Ok;(Object.keys(gauseen) as Array<keyof IPerson>).map((key) => gauseen[key])

最初

欢送关注无广告文章公众号:学前端

参考

  • TypeScript: Interfaces vs Types
  • What does the is keyword do in typescript?
  • type-assertion
  • 译 TypeScript 3.0: unknown 类型