阅读 258

15 个提高开发效率的 TypeScript 小技巧

TypeScript 相信大家都见的很多了,但总有些时候不知道类型怎么写才能不报错

有时候你满脑子疑惑“哈?这都能报错?我写的明明是对的啊,到底该怎么写?”

来看看下面的小技巧吧,总有一个能提高你的效率

提取元组或数组类型中每一项的类型

type ArrayType1 = Array<{
    a: number
    b: number
}>
type ArrayType2 = ({
    a: number
    b: number
} | {
    c: string
    d: string
})[]
// 通过索引访问来获取,我们都知道数组的索引是 number 类型的
type GetArrayOrTupleItemType1 = ArrayType1[number]
// 得到
type GetArrayOrTupleItemType1 = {
    a: number
    b: number
}
// 通过 infer 进行推导
type GetArrayOrTupleItemType2 = ArrayType2 extends Array<infer U> ? U : never
// 得到
type GetArrayOrTupleItemType2 = {
    a: number
    b: number
} | {
    c: string
    d: string
}
复制代码

获取接口 interface (或对象类型)中的类型

interface A {
    b: string
    c: number
    d: Array<{
        e: symbol
    }>
}
type B = A['b']
type C = A['c']
// 与上一小节的技巧配置使用
type E = A['d'][number]['e']
复制代码

const 和 let 类型推导的区别

这在 ts 中叫做 类型推断

const 声明的常量的类型为字面量类型

const a = 1 // 则 a 的类型就为 1
const d = '2' // 则 a 的类型就是 '2'
// 这是因为常量是不可修改的,所以不会进行类型推断,但下面的情况不是这样
// b 的类型为 number,c 的类型为 string,这是由于右侧得到的类型即为 (number | string)[]
const [b, c] = [1, '2']
// d.e 的 类型 为 number,d.f 的类型 为 string,因为常量对象的属性是可以进行操作的,类型推导也会发生在初始化成员(对象属性)的过程中
const d = {
    e: 1,
    f: '2'
}
复制代码

let 声明的变量时,类型会被推导

let a = 1 // a 为 number 类型
let b = '2' // b 为 string 类型
复制代码

as const 进行断言

上一小节中 let 声明的 a 和 b 如何得到它们初始值本身对应的字面量类型?

let a: 1 = 1 // 1 本身也可以作为类型使用
// 同样的也可以进行断言
let a = 1 as 1
// 也可以用 as const 更加统一,明了
let a = 1 as const
let b = '2' as const
let c = {
    e: 1 as const
}
let d = [1 as const]
复制代码

常用于接收类型为字面量类型的情况,如

const a = {
    b: 1
}
const c = (params: { b: 1 }) => {}

c(a) // 报错:不能将类型“number”分配给类型“1”,在 b: 1 那一行最后加上 as const 即可解决
复制代码

as unknown as xxx 代替 as any

开发中经常会遇到类型定义的不太好,需要用 as 进行断言的情况,简单来看,可以直接用 as any 解决几乎所有的 ts 类型问题(如报错)

但不利于后续的维护,维护者可能并不知道被 as any 的目标应该是什么类型,正确的做法应该是用 as unknown as xxx 代替,这样能看到明确的类型,如

// 真实场景可能更为复杂
;(window as unknown as { handler: (() => void) | null }).handler = null
复制代码

更多内容参考我的这篇文章TypeScript进阶, 如何避免 any

非空断言符

有时候定义了某个变量或对象属性会包含 undefined 或 null 类型,但使用时逻辑上一定不存在未定义的情况

那么就可以使用非空断言

let a: undefined | {
  b: string
} = undefined
function c() {
  a = {
    b: '1'
  }
}
c()
console.log(a.b) // 提示:对象可能为“未定义”
// 在可能为 undefined 或 null 的变量或对象属性后增加 ! 非空断言操作符
console.log(a!.b)
复制代码

可选链操作符

大部分情况下,可选链操作符 ?. 会被编译,如果它前面的变量或对象属性为空,则会直接返回 undefined,新版 Chrome 浏览器已经支持这一特性,可以直接使用

declare 关键字

declare 用于声明那些当前模块下没有,但实际上可以被访问的变量常量或方法等

declare const a: string
declare function b(): void
declare class C {}
// 这样使用常量 a 和函数 b 都不会报错
// 比如有个 jquery 对象是通过 script 形式引入的,那么在项目的 .d.ts 文件中这样写,可以让它在任何地方都能被访问
declare const $: JQueryStatic
// 当然,declare module 'jquery' 也是可以的
复制代码

有限元素的数组(元组)类型声明以及某一项(或多项)可为 undefined 的情况

type DateArray = [Date, Date]
// 可为空
type DateArray1 = [Date | undefined, Date]
// 或直接在可为空的元素后面加 ?
type DateArray2 = [Date?, Date?]
复制代码

函数类型中的参数关系限定

/* 如果 a 为 1 的时候,b 必须 传 '1',a 为 2,b 必须传 2 */
/*  这样写无法限定两者的关系 */
function a(a: 1 | 2, b: '1' | '2') {
    // xxx
}
/* 可以利用重载 */
function a(a: 1, b: '1'): void
function a(a: 2, b: '2'): void
function a(a: 1 | 2, b: '1' | '2'): void {}
/* 也可以利用泛型 */
interface BType {
    1: '1'
    2: '2'
}
/* 只有在泛型中使用 extends 约束,并且 ab 确实有某种关系,才能在 a 为特定类型的时候,约束 b 的类型 */
function b<AType extends keyof BType>(a: Atype, b: BType[AType]): void {}
/* 或 */
function b<AType extends keyof BType>(a: Atype, b: AType extends '1' ? BType['1'] : BType['2']): void {}
/* 
 * 你也可以用上述两种方式来声明参数与返回值的对应关系,泛型与 extends 的特性也可以用于类的构造器的声明
 * 注意,如果函数只有一个参数,你也可以这样写
 */
function c({ a, b }: { a: 1; b: '1' } | { a: 2; b: '2' }): void {}
复制代码

如何得到异步函数返回值的具体类型?

declare function a(): Promise<{
    b: 1
    c: '2'
}>
async function b() {
    // 也就是这个 c 的类型,不调用 a 的情况如何获取呢?
    const c = await a()
}
// 先能获得 Promise 泛型中传入的的类型
type GetPromiseType<P extends unknown> = P extends Promise<
  infer Params
>
  ? Params
  : P
// 这样就能得到了(ReturnType 是自带的工具类型)
type GetAsyncFunctionReturnType = GetPromiseType<ReturnType<typeof a>>
复制代码

提示找不到具有类型为 "string" 的参数的索引签名

const a = {
    b: 1,
    c: 2
}
let c: string = 'b'
// 元素隐式具有 "any" 类型,因为类型为 "string" 的表达式不能用于索引类型 "{ b: number; c: number; }"。
// 在类型 "{ b: number; c: number; }" 上找不到具有类型为 "string" 的参数的索引签名。
const d = a[c] // 这一行报错

// 这样定义 a 就好了
const a: { [key: string]: number } = {
    b: 1,
    c: 2
}
// 如果你的 a 已声明类型了
interface A {
    b: number
    c: number
}
const a: A = {
    b: 1,
    c: 2
}
// 那加在后面也是可以的
const a: A & { [key: string]: number } = {
    b: 1,
    c: 2
}
复制代码

各种自带的实用工具类型

参考我的这篇文章TypeScript 的 Utility Types,你真的懂吗?

得到对象类型值类型的联合类型

interface A {
    a: 1
    b: 2
    c: '3'
}
// 要得到 1 | 2 | '3'
// 则
type Values = A[keyof A]
复制代码

如何重新导出一个类型声明

// a.ts 文件
export interface A {
    a: 1
    b: '2'
}
// index.ts 文件
// 方式一,重新执行类型别名
import { A } from './a.ts'
export type AType = A
// 方式二
export { A } from './a.ts'
// 注:配置了 --isolatedModules 需要这样写
export type { A } from './a.ts'
// 方式三
export * from './a.ts'
复制代码
文章分类
前端
文章标签