Typescript 的严格模式实践

4/13/2020 TypescriptNode

开启了严格模式后,可以帮助我们提前发现很多 BUG,关于严格模式的详细介绍,可以看看这篇文章 (opens new window)

接下来,我将在一个实际项目上打开严格模式,看看要如何落地

# Property has no initializer

变量声明后没有初始化

这种问题要根据实际情况来解决,需要初始化的补上即可。 在这个例子中,这是一个 model 对象,我们明确知道 mongoose 会帮我们做好初始化的,我们可以这样声明字段:

name!: string
1

感叹号的意思是,告诉 type checker:“这个字段你就不用担心初始化问题了,我们开发会搞定的”。

除了这个方式,我们还有其他的解决方案,具体可以看看这篇文章 (opens new window)

# Parameter is any type

这是一个很常见的场景,用 query 表示 object 参数,至于 query 里面有什么东西,请允许我引用一个经典笑话:

当我写下这一行代码时,只有我和上帝知道是什么意思。一个月后,只有上帝才知道是什么意思了…

我们可以在开发规范里要求大家写文档,但这种约束力明细不如 “不写清楚类型就不让你执行” 来得稳妥。

所以,这种情况下,严格模式的 typescript 会报错:

Error:(18, 18) TS7006: Parameter 'query' implicitly has an 'any' type.

不允许 any 类型的参数,所以我们必须为参数声明类型

export class UsersQueryDto {
  @IsNotEmpty()
  readonly userId!: string
}
1
2
3
4

然后标记类型:

@Get()
async findAll (query : UsersQueryDto): Promise<IUserModel[]> {
    return this.usersService.findAll(query)
}
1
2
3
4

当然了,不像 Java 那么死板,也不是所有参数都要搞一个类来声明类型的,声明类型也可以更简单些:

@Get()
async findAll (query : {userId : string}): Promise<IUserModel[]> {
    return this.usersService.findAll(query)
}
1
2
3
4

是否要声明一个类,取决于是否要将这个参数类型在其他地方共享

# Module need types

我们直接引入一个包,以 mongoose 为例

import * as mongoose from 'mongoose'
1

会报错:

Error:(3, 27) TS7016: Could not find a declaration file for module 'mongoose'. 'klg-nest-starter/node_modules/mongoose/index.js' implicitly has an 'any' type.
  Try `npm install @types/mongoose` if it exists or add a new declaration (.d.ts) file containing `declare module 'mongoose';`

安装提示把对应的 types 安装以下即可,执行:

npm install @types/mongoose
1

如果你使用的包没有提供 types,那就需要你自己编写 types,具体可以参考其他包的写法。 不过 typescript 目前的火热程度,稍微有人气的包都会提供 types,或者很多包干脆就是直接用 typescript 编写的,不用太担心这个问题。

# Nullable

在有了 @types/mongoose 的基础上,我们可以提前发现更多问题了

mongoose 对 findById 的返回值定义是 T | null

findById(id: any | string | number,
      callback?: (err: any, res: T | null) => void): DocumentQuery<T | null, T, QueryHelpers> & QueryHelpers;
1
2

所以这里 user 的查询结果可能会 null,直接是用 user.id 可能会出现错误。 修复方案:

  async getAccountAndUser (userId: string): Promise<IAccountModel> {
    let user = await this.userModel.findById(userId)
    if (!user) throw new Error('User not found ' + userId)
    let account = await this.accountModel.findOne({userId: user.id})
    if (!account) throw new Error('Account not found ' + user.id)
    return account
  }
1
2
3
4
5
6
7

# Type Match

export async function genFixturesTemp (template: object, nums: number, modelName: string, fixData?: Function) {
  if (!fixData) fixData = (index: number, it: object) => it
  let items = Array(10).fill(0).map(fixData)
  return items
}

1
2
3
4
5
6

这段代码里有个 bug,能一眼看出来吗?

typescript 给了我们一个提示:fixData 这个函数和 map 的参数不匹配

原来 map 函数对参数的定义是

map<U>(callbackfn: (value: T, index: number, array: T[]) => U, thisArg?: any): U[];
1

第一个参数 value 第二个参数才是 index:number 修复方案:

export async function genFixturesTemp (template: object, nums: number, modelName: string, fixData?: (it: object, i: number) => any) {
  if (!fixData) fixData = (it: object, index: number) => it
  let items = Array(10).fill(0).map(fixData)
  return items
}
1
2
3
4
5

调整函数,并准确声明 Function 的类型

Last Updated: 7/20/2022, 5:50:20 PM