1. 联合类型
TS可以对类型进行各种操作,包括与(&)、并(|)等。
1.1 联合类型描述
// 联合类型 Union Type
type A2 = { name: string }
type B2 = { age: number }
type C2 = A2 | B2
const c2: C2 = {
name: 'lwz',
age: 18,
}
console.log(c2)
// 在声明的使用是联合类型,而在使用的时候是类型收缩(收窄narrowing)
const f1 = (a: number | string) => {
if (typeof a === 'number') {
a.toFixed(2)
} else if (typeof a === 'string') {
parseFloat(a).toFixed(2)
} else {
throw new Error('Never do this')
}
}
1.2 类型收窄
- typeof
/**
* typeof 只能返回如下几种类型:
* string/number/bigInt/boolean/symbol/undefined
* object/function
* can't return null
* limitation:
* typeof 数组对象
* typeof 普通对象
* typeof 日起对象
* typeof null
* 以上四种类型 typeof的返回结果统一是object
*/
- instanceof
/**
* 使用 instanceof 来区分类型
* limitation
* 1. 不支持基本类型判断
* 2. 不支持TS独有的类型
* 3. 使用 typeof 和 instanceof 可以解决大部分问题
* 4. 当使用上面方法无法判断时,可以使用in方法判断
*/
const f1 = (a: Array<Date> | Date) => {
if (a instanceof Date) {
a.toISOString()
} else if (a instanceof Array) {
a[0].toISOString()
} else {
throw new Error('Never do this')
}
}
const f2 = (a: Date | string | Date[]) => {
if (typeof a === 'string') {
a.toLocaleLowerCase()
} else if (a instanceof Date) {
} else {
}
}
- in
type Person = {
name: string
}
type Animal = {
x: string
}
const f3 = (a: Person | Animal) => {
if ('name' in a) {
} else if ('x' in a) {
} else {
}
}
- ts自动推导
// 以上几种判断方法都是使用JS中判断类型的函数来区分
const f4 = (a: string | string[]) => {
if (Array.isArray(a)) {
a.join('\n').toString()
// 此处 a 的类型是 string[]
} else if (typeof a === 'string') {
parseFloat(a).toFixed(2)
} else {
throw new Error('Never do this')
}
}
// ts自动推导功能
const f5 = (x: string | number, y: string | boolean) => {
if (x === y) {
x // string
y // string
} else {
x // string | number
y // string | boolean
}
}
- 类型谓词is
type Rect = {
height: number
width: number
}
type Circle = {
center: [number, number]
radius: number
}
const f1 = (a: Rect | Circle) => {
if (isRect(a)) {
a
}
}
function isRect(x: Rect | Circle): x is Rect {
return 'height' in x && 'width' in x
}
// 使用箭头函数会报错
// const isRect(x: Rect | Circle): x is Rect => {
// return 'height' in x && 'width' in x
// }
/**
* is 优点:
* 可以声明所有的类型
* 缺点:
* 麻烦, 需要写好多东西
*/
- 可辨别类型
type A = { kind: 'string'; value: string }
type B = { kind: 'number'; value: number }
const f1 = (a: A | B) => {
if (a.kind === 'string') {
a
} else {
a
}
}
type Circle = { kind: 'Circle'; center: [number, number] }
type Square = { kind: 'Square'; sideLength: number }
type Shape = Circle | Square
const f2 = (a: string | number | Shape) => {
// 首先使用typeof或者instanceof排除掉简单类型
if (typeof a === 'string') {
a
} else if (typeof a === 'number') {
a
} else if (a.kind === 'Circle') {
a
} else {
a
}
}
/**
* 优点:
* 让复杂类型的收窄变成简单类型的对比
* 要求:
* 1. A, B, C, D... 有相同属性kind或者其他category
* 2. kind的类型是简单类型
* 3. 各类型中的kind 可以有交集,但是需要包含各自特有的属性
* 具有以上特点的 T 称为 可辨别类型
* 一句话总结: 同名且可辨别的简单类型的key
*/
- 法外狂徒any
// 思考: any 是所有类型的联合吗? 为什么
// 答案不是 (反证法)
// 联合类型一旦排除某个类型后就不能使用其他类型的方法
// 而any 是可以使用所有的方法
// any = 法外狂徒
// TS 绝大部分 规则对any不生效
// const f1 = (a: any) => {
// const b: never = a
// }
// 什么 = 所有类型(除了 never/unknown/any/void)的联合? 为什么
// unknown 答案见下
- 重新认识unknown
const f1 = (a: object) => {
if (typeof a === 'string') {
a
}
}
// const f2 = (a: unknown) => {
// if (typeof a === 'string') {
// a
// } else if (typeof a === 'number') {
// a
// }
// }
// type isPerson = {
// name: string
// age: number
// }
// const f3 = (a: unknown) => {
// if (a instanceof Date) {
// a
// } else if (isPerson(a)) {
// }
// }
2. 交叉类型
- 交叉类型并集
type 有左手的人 = {
left: string
}
// 这种写法没有问题
// const a: 有左手的人 = {
// left: 'yes'
// }
// 这种写法有问题
// const a: 有左手的人 = {
// left: 'yes',
// right: 'yes'
// }
// 修改写法
const b = {
left: "yes",
right: "yes"
}
const a: 有左手的人 = b
console.log(a)
- 交叉类型补充
type Person = {
id: string
name: string
}
type User = {
id: number
email: string
} & Person
const a: User = {
name: "lwz",
email: "x",
id: 1 as never
}
console.log(a.id)
// interface Person {
// id: string
// name: string
// }
// 当对外提供接口的时候,为方便扩展,此时使用interface要比使用type方便
// interface在声明的时候就会报错,而type只有在编译的时候才会报错
// interface User extends Person {
// id: number
// email: string
// }
// const a: User = {
// name: "lwz",
// email: "x",
// // 此时id没有意义
// id: 1 as never
// }
// console.log(a.id)
type A = {
method: (n: number) => void
}
type B = {
method: (n: string) => void
} & A
const b: B = {
method: n => {
console.log(n)
}
}
console.log(b.method(1))
// 函数的交集会得到一个参数的并集
type F1 = (n: number) => void
type F2 = (n: string) => void
type X = F1 & F2
const x: X = n => {
console.log(n)
}
console.log(x("abc"))
// 交叉类型常用于有交集的类型A、B
// 如果A、B无交集 1) 可能得到never,也可能只是属性为never
3. 类型兼容
3.1 为什么需要类型兼容
const config = {
a: "111",
b: "222",
c: "333",
d: "444"
}
// const returnCg = config => lodash.pick(config, ["a", "b", "c"])
// returnCg(config)
// 当我们只需要三个值的时候,此时传了4个值,这种情况下需要类型兼容
3.2 简单类型和普通对象如何兼容
type A = string | number
// 类型小的可以代替类型大的
const a: A = "hi"
// 获取类型 typeof
// 当需要给user的对象声明一个类型的时候用到typeof
let user = {
name: "1",
age: 18,
id: 1,
email: "xxx"
}
// type User = typeof user
// 限制少的可以代替限制多的
type Person = {
name: string
age: number
}
type User = {
name: string
age: number
id: number
email: string
}
const u: User = {
name: "lwz",
age: 18,
id: 1001,
email: "xxx"
}
const p: Person = u
// User 作为参数也可以传递,此种情况下相当于多传了但没有用到值
3.3 接口继承
// 当两个接口没有父子继承关系的时候也是支持类型兼容的
interface 有左手的人 {
left: string
}
interface 有双手的人 {
left: string
right: string
}
let person: 有双手的人 = {
left: "yes",
right: "yes"
}
let personLeft: 有左手的人 = person
3.4 函数参数兼容
interface MyEvent {
timestamp: number
}
interface MyMouseEvent extends MyEvent {
x: number
y: number
}
function listenEvent(eventType: string, handler: (n: MyEvent) => void) {
/* ... */
}
// 我们希望这样用
// tsconfig.json 中配置 strictFunctionTypes: false
listenEvent("click", (e: MyMouseEvent) => console.log(e.x + "," + e.y))
// 但只能这样用
listenEvent("click", (e: MyEvent) => console.log((e as MyMouseEvent).x + "," + (e as MyMouseEvent).y))
// 还可以这么用
listenEvent("click", ((e: MyMouseEvent) => console.log(e.x + "," + e.y)) as (e: MyEvent) => void)
// 这个就太离谱了
listenEvent("click", (e: number) => console.log(e))
3.5 函数返回值兼容
let 返回值属性少集合大 = () => {
return { name: "Alice" }
}
let 返回值属性多集合小 = () => {
return { name: "Alice", location: "Seattle" }
}
// OK
返回值属性少集合大 = 返回值属性多集合小
// 报错:'location' is missing
返回值属性少集合小 = 返回值属性多集合大
3.6 JSON类型值
type JSONValue = string | number | null | boolean | JSONValue[] | { [k: string]: JSONValue }