共计 5162 个字符,预计需要花费 13 分钟才能阅读完成。
盘点 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
unknown
是 any
类型对应的平安类型,在对 unknown
类型的值执行大多数操作之前,咱们必须进行某种模式的查看。而在对 any
类型的值执行操作之前,咱们不用进行任何查看
let value: unknown
value = undefined // ok
value = null // ok
value = true // ok
value = 86 // ok
value = 'hello' // ok
value = {} // ok
value = Symbol() // ok
对 unknown
类型大多数操作都认为是谬误的
let value: unknown
let v1: unknown = value // ok
let v2: any = value // ok
let v3: undefined = value // Error
let v4: null = value // Error
let v5: string = value // Error
let v6: number = value // Error
let v7: boolean = value // Error
let v8: symbol = value // Error
console.log(value.key) // Error
value.foo() // Error
所以在操作 unknown
类型前,应该放大类型范畴,能够通过:typeof、instanceof、as、is
let value: unknown = 'hello'
// 通过 typeof 放大类型范畴
if (typeof value === 'string') {console.log(value.length)
}
void
void
示意没有任何类型,只能将它赋值为 undefined
和 null
// 用 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
类型,然而,当一个函数基本就没有返回值时(陷入死循环或者总是抛出谬误),它返回一个 never
。void
指能够被赋值的类型(在 strictNullChecking 为 false 时),其余任何类型不能赋值给 never
,除了 never
自身以外
interface VS type
开发中,常常用 interface
或 type
用于类型申明,对与老手来说非常容易混同,上面梳理一下它们之间的相同点与不同点。
相同点
定义对象或函数
两者都能够定义对象或者函数,然而语法有所不同,示例如下:
// interface
interface Point {
x: number
y: number
}
interface SetPoint {(x: number, y: number): void
}
// type
type Point = {
x: number
y: number
}
type SetPoint = (x: number, y: number) => void
Extend(继承)
两者都能够 extends
(继承),但语法不同。包含,interface
能够继承 type
,type
也能够继承 interface
。
⚠️ interface
不能够继承 type
的联结类型
// interface extends interface
interface PointX {x: number}
interface Point extends PointX {y: number}
// type alias extends type alias
type PointX = {x: number}
type Point = PointX & {y: number}
// interface extends type alias
type 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 interface
interface 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 认为 foo
为 any
类型,所以 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
兼容B
或B
兼容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')
// OK
function getValue(obj: IPerson, key: keyof IPerson) {return obj[key]
}
getValue(gauseen, 'age')
keyof
keyof
取 interface
的键,返回值可作为一个联结类型
interface IPerson {
age: number
name: string
weight: string
}
const gauseen: IPerson = {
age: 26,
name: 'gauseen',
weight: '65kg',
}
// Error
Object.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
对象中的某个键,如下:
// Pass
type 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 类型