Skip to content
导航栏

TS类型检查机制

作者:winter wang
更新于:11 天前
字数统计:1.6k 字
阅读时长:6 分钟
阅读量:

TS编译器在做类型检查时,所秉承的一些原则,以及表现出的一些行为。

作为:辅助开发,提高开发效率

  • 类型推断
  • 类型兼容性
  • 类型保护

类型推断

不需要指定变量的类型(函数返回值类型),TS可以根据某些规则自动地为其推断出一个类型

  • 基础类型推断
  • 最佳通用类型推断
  • 上下文类型推断

基础类型推断

ts
let a // any
const b = 1 // number
const c = [] // any[]
const d = [1] // number[]
  • 声明变量的时候
  • 设置函数默认参数
  • 确定函数返回值

最佳通用类型推断

当有多个类型的时候,会推断成最佳通用的类型

ts
const b = [1, 'apple'] // (number | string)[]

上下文类型推断

从左到右推断,通常发生在事件处理中

ts
window.onkeydown = (event) => { // event: KeyboardEvent
  console.log(event)
}

根据左侧的事件绑定,推断右侧的事件类型

类型断言

如果TS推断的类型不符合你的预期,可以使用类型断言覆盖TS的推断

ts
const foo = {}
foo.bar = 1

这样会报错,可以这样使用类型断言解决

ts
interface Foo {
  bar: number
}

const foo = {} as Foo
foo.bar = 1

// 最好还是在声明的时候就指定类型
const foo1: Foo = {
  bar: 1
}

类型兼容性

当一个类型Y可以被赋值给另一个类型X时,我们就可以说类型X兼容类型Y

X兼容Y: X(目标类型)=Y(源类型),也就是说Y是X的子类型

设置 strictNullChecks: false

ts
let s = 'a'
s = null // 字符串变量可以被赋值给null

字符串类型是兼容null类型的,null是字符串类型的子类型

接口的兼容性

ts
interface X {
  a: any
  b: any
}

interface Y {
  a: any
  b: any
  c: any
}

let x: X = { a: 1, b: 2 }
let y: Y = { a: 1, b: 2, c: 3 }

x = y // X类型可以兼容Y类型:鸭式辩型[源类型具备目标类型的必要属性;能多不能少]
y = x // Property 'c' is missing in type 'X' but required in type 'Y'.

接口之间相互兼容:成员的会兼容成员的.

函数的兼容性

首先定义一个函数类型,再定义一个高阶函数,接受这个函数类型的函数

ts
type Handler = (a: number, b: number) => void

function hof(handler: Handler){
  return handler
}

#### 参数个数

函数参数固定的时候

ts
const handler1 = (a: number) => {}
hof(handler1) // 可以少传

const handler2 = (a: number, b: number, c: number) => {}
hof(handler2) // Argument of type '(a: number, b: number, c: number) => void' is not assignable to parameter of type 'Handler'.

函数参数不固定的时候:可选参数与剩余参数

ts
let a = (x: number, y: number) => {}
let b = (x?: number, y?: number) => {}
let c = (...args: number[]) => {}

a = b
a = c
b = a // error
b = c // error
c = a
c = b
  • 固定参数 兼容 可选参数与剩余参数
  • 可选参数 不兼容 固定参数与剩余参数 (通过 strictFunctionTypes: false 可以关闭)
  • 剩余参数 兼容 固定参数与可选参数

参数类型

ts
const handler3 = (a: string) => {}
hof(handler3) // error

如果函数参数类型是对象类型的呢?

ts
interface Point2D {
  x: number
  y: number
}

interface Point3D {
  x: number
  y: number
  z: number
}

let p2d = (point: Point2D) => {}
let p3d = (point: Point3D) => {}

p3d = p2d
p2d = p3d // error (通过 `strictFunctionTypes: false` 可以关闭)

参数多的兼容参数少的

返回值类型

ts
let f1 = () => ({ x: 1 })
let f2 = () => ({ x: 1, y: 2 })
f1 = f2
f2 = f1 // error

与鸭式辩型法一样,成员少的兼容成员多的

函数重载

ts
function overload(a: number, b: number): number // 目标函数【参数多于源函数,类型小于源函数】
function overload(a: string, b: string): string // 目标函数
function overload(a: any, b: any): any {} // 具体实现:源函数

枚举的兼容性

数字和枚举可以相互兼容,枚举与枚举间不兼容

ts
enum Fruit { Apple, Banana }
enum Color { Red, Yellow }

let fruit: Fruit.Apple = 11
let num: number = Fruit.Apple

let color = enum: Color.Red = Fruit.Apple // 报错

类的兼容性

类的构造函数和静态成员不参与比较

ts
class A {
  constructor(p: number, q: number) {}
  id = 1
}
class B {
  static s = 1
  constructor(p: number) {}
  id = 2
}

let aa = new A(1, 2)
let bb = new B(1)

aa = bb // 实例相互兼容
bb = aa // 实例相互兼容

当类中有私有成员时,两个类不兼容,类与子类可以

ts
class A {
  constructor(p: number, q: number) {}
  id = 1
  private name = ''
}
class B {
  static s = 1
  constructor(p: number) {}
  id = 2
  private name = ''
}

let aa = new A(1, 2)
let bb = new B(1)

aa = bb // error
bb = aa // error

class C extends A {}
let cc = new C(1, 2)
aa = cc // 父类和子类的实例相互兼容
cc = aa // 父类和子类的实例相互兼容

泛型的兼容性

1)泛型接口 在两个泛型参数只有类型不相同时,只有在泛型参数使用时才影响

ts
interface Empty<T> {}

let obj1: Empty<number> = {}
let obj2: Empty<string> = {}

obj1 = obj2
obj2 = obj1

接口使用了类型参数,就会影响兼容性

ts
interface Empty<T> {
  value: T
}

let obj1: Empty<number> = {}
let obj2: Empty<string> = {}

obj1 = obj2 // error
obj2 = obj1 // error

2)泛型函数

ts
let log1 = function<T>(x: T): T {
  console.log('x')
  return x
}

let log2 = function<U>(y: U): U {
  console.log('y')
  return y
}

log1 = log2
log2 = log1

总结

结构之间进行比较:成员少的兼容成员多的(我要的你都有,那我兼容你) 函数之间进行比较:参数多的兼容参数少的(你给我太多我处理不了,我不能兼容你)

类型保护

TS能够在特定的区块中保证变量属于某种确定的类型,可以在区块中放心的引用此类型的属性,或者调用此类型的方法

ts
class Java {
  helloJava() {
    console.log('hello Java')
  }

  java: any
}

class JavaScript {
  helloJavaScript() {
    console.log('hello JavaScript')
  }

  javascript: any
}

function getLanguage(type: Type) {
  const lang = type === Type.Strong ? new Java() : new JavaScript()
  // TODO
  return lang
}

getLanguage(Type.Strong)

instanceof

判断一个实例是否属于某个类

ts
function getLanguage(type: Type){
  let lang = type === Type.Strong ? new Java() : new JavaScript() 
  // TODO
  if(lang instancof Java){
    lang.helloJava()
  }else {
    lang.helloJavaScript()
  }
  return lang
}

in

判断对象中是否包含某个属性

ts
function getLanguage(type: Type) {
  const lang = type === Type.Strong ? new Java() : new JavaScript()
  // TODO
  if ('java' in lang)
    lang.helloJava()

  else
    lang.helloJavaScript()

  return lang
}

typeof

ts
function test(x: string | number) {
  if (typeof x === 'string')
    x.length

  else
    x.toFixed(2)

}

创建类型保护函数

类型谓词 lang is Java

ts
function isJava(lang: Java | JavaScript): lang is Java {
  return (lang as Java).helloJava !== undefined
}

// 创建类型保护区块
if (isJava(lang))
  lang.helloJava()

else
  lang.helloJavaScript()

Contributors

winter wang