代码规范 - 编辑器 & TS 配置

402 阅读7分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第5天

在同一个项目中有多个人员参与时,每个人的能力、编码风格和习惯都是不同的,如果没有代码规范的话,随着项目发展,项目的健壮性会越来越差,因此需要统一的规则来进行约束。

EditorConfig 编辑器配置

EditorConfig 主要用于统一不同 IDE 编辑器的编码风格。

在项目根目录下添加 .editorconfig 文件:

# 表示是最顶层的 EditorConfig 配置文件
root = true

# 表示所有文件适用
[*]
# 缩进风格(tab | space)
indent_style = space
# 控制换行类型(lf | cr | crlf)
end_of_line = lf
# 设置文件字符集为 utf-8
charset = utf-8
# 去除行首的任意空白字符
trim_trailing_whitespace = true
# 始终在文件末尾插入一个新行
insert_final_newline = true

# 表示仅 md 文件适用以下规则
[*.md]
max_line_length = off
trim_trailing_whitespace = false

# 表示仅 ts、js、vue、css 文件适用以下规则
[*.{ts,js,vue,css}]
indent_size = 2

部分 IDE 中需要安装对应插件才能支持,如:VSCode、Atom、Sublime Text 等。

具体列表可以参考官网,如果在 VSCode 中使用需要安装 EditorConfig for VS Code 插件。

在使用Vite创建项目时使用的是vue-ts模板,所以在创建项目的时候package.json就自带了typescript

在初始化 vite 项目的时候就可以看到,项目根目录下生成了 tsconfig.jsontsconfig.node.json 两个文件。

tsconfig.json 配置项

在初始化 vite 项目的时候可以看到,项目根目录下生成了 tsconfig.jsontsconfig.node.json 两个文件。

tsconfig.json 指定了用来编译这个项目的根文件和编译选项

{
  "compilerOptions": {
    "target": "esnext",
    "useDefineForClassFields": true,
    "module": "esnext",
    "moduleResolution": "node",
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "lib": ["esnext", "dom"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"],
  "references": [{ "path": "./tsconfig.node.json" }]
}

可以看到tsconfig.node.json中的"include": ["vite.config.ts"]表明这个单独拆分出来的配置文件只是负责编译 vite 的配置文件vite.config.ts

{
  "compilerOptions": {
    "composite": true,
    "module": "esnext",
    "moduleResolution": "node"
  },
  "include": ["vite.config.ts"]
}

include 指定需要编译的文件范围

默认是对 src 目录下的 .ts、.d.ts、.tsx、.vue结尾的文件都需要进行编译,这里扩展下和src同级的tests、build目录(如果项目中需要的话)

 "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "build/**/*.ts", "tests/**/*.ts", "tests/**/*.tsx"]

exclude 配置不需要编译的文件范围

"exclude": ["node_modules", "tsconfig.json", "dist"]

references 将 TypeScript 程序拆分

references 是 TypeScript 3.0 的新特性,允许将 TypeScript 程序拆分结构化。这和我们写 vue 组件的思想有着异曲同工之妙,避免了在一个文件中处理过多的内容,而是通过拆分成多个文件,分别配置不同的部分,达到更加清晰,可维护性更高的目的。

这里我们可以不拆分,可以删除掉tsconfig.node.json统一用tsconfig.json,并移除默认的:

 "references": [{ "path": "./tsconfig.node.json" }]

compilerOptions 编译选项

useDefineForClassFields

useDefineForClassFields 是 TypeScript 3.7.0 中新增的一个编译选项,启用后的作用是将 class 声明中的字段语义从 [[Set]] 变更到 [[Define]]

zhuanlan.zhihu.com/p/258906525

skipLibCheck 忽略声明文件检查

忽略所有的声明文件( *.d.ts)的类型检查

"compilerOptions": {
    "skipLibCheck": true
}

这个属性不但可以忽略 npm 不规范带来的报错,还能最大限度的支持类型系统。设置为 true 就不用怕使用的第三方库不规范了。

为什么要跳过这些第三方库的检查?

我们做类型检查(以及代码规范等)的目的是为了对团队内业务代码开发保持统一和规范,以保证开发成员之间的快速上手和后续维护。所以我们要做的是将各种规则集成到业务代码模块,而一些框架上的或者第三方库的内容就不用多此一举了。

baseUrl

作用:设置baseUrl来告诉编译器到哪里去查找模块。所有非相对模块导入都会被当做相对于 baseUrl

注意相对模块的导入不会被设置的baseUrl所影响,因为它们总是相对于导入它们的文件.

这个设置的作用和 vite 构建选项 build 中的 base 是类似的,我们配置为当前根目录即可

paths 路径映射列表

作用:模块名到基于 baseUrl的路径映射的列表。

请注意"paths"是相对于"baseUrl"进行解析。

在 vite 配置中设置了路径别名resolve.alias,为了让编译 ts 时也能够解析对应的路径,我们还需要配置 paths 选项

"paths": {
  "@/*": ["src/*"],
  "utils/*": ["src/utils/*"],
  "api/*": ["src/api/*"],
  "components/*": ["src/components/*"],
  "views/*": ["src/views/*"],
  "#/*": ["types/*"],
  "build": ["build/*"]
},

isolatedModules 将每个文件作为单独模块

typescript 将没有导入/导出的文件视为旧脚本文件。因为这样的文件不是模块和它们在全局命名空间中合并的任何定义。该配置项会禁止此类文件。将任何导入或导出添加的文件都视为模块

这个设置在 vite 官方文档中是被要求应该设置为 true 的,但(vite2)项目初始化的时候并没有默认加上这条(vite3加上了)

"isolatedModules": true,

vitejs.cn/guide/featu…

types 客户端类型

作用:添加要包含的类型声明文件名列表,只有在这里列出的模块的声明文件才会被加载进来

{
  "compilerOptions": {
    "types": ["vite/client"]
  }
}

这将会提供以下类型定义补充:

  • 资源导入 (例如:导入一个 .svg 文件)

  • import.meta.env 上 Vite 注入的环境变量的类型定义

  • import.meta.hot 上的 HMR API 类型定义

tsconfig.json文件首行报错

vscode会自动进行文件的语义检查。因为自定义的tsconfig.json文件无法覆盖vscode自带的配置,所以会报错。

打开配置settings.json文件,让自定义的jsconfig.js文件覆盖vscode默认选项。

3.2. env.d.ts 全局类型声明

打开 src 文件夹下的 env.d.ts (vite3为vite-env.d.ts)文件,可以看到

/// <reference types="vite/client" />

declare module '*.vue' {
  import type { DefineComponent } from 'vue'
  // eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
  const component: DefineComponent<{}, {}, any>
  export default component
}

请注意:三斜杠指令是包含单个XML标签的单行注释,注释的内容会做为编译器指令使用,只有在文件的最顶部才会生效,可以查看TypeScript: Documentation - Triple-Slash Directives学习相关内容。

所以这个文件的作用就呼之欲出了:帮助编译器识别类型

TypeScript 相比 JavaScript 增加了类型声明。原则上,TypeScript 需要做到先声明后使用。这就导致开发者在调用很多原生接口(浏览器、Node.js)或者第三方模块的时候,因为某些全局变量或者对象的方法并没有声明过,导致编译器的类型检查失败。

当我们遇上Property xxx does not exist on type ...报错的时候,就可以定位到是没有声明过这个方法/属性。我们就可以在这个文件作全局声明。

可以看到,该文件已经默认为所有的 vue 文件声明了 DefineComponent的组件类型,这就意味着只要我们的单文件组件使用

<script lang="ts">
  import { defineComponent } from 'vue'
  export default defineComponent({
    ...
  })
</script>

的写法,就能避免 vue 文件中大多数类型声明的报错,比如使用路由的this.routerthis.router、this.route命令

完整配置文件

原代码

{
  "compilerOptions": {
    "target": "ESNext",
    "useDefineForClassFields": true,
    "module": "ESNext",
    "importHelpers": true,
    "moduleResolution": "Node",
    "experimentalDecorators": true,
    "allowSyntheticDefaultImports": true,
    "strict": true,
    "jsx": "preserve",
    "sourceMap": true,
    "resolveJsonModule": true,
    "isolatedModules": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "types": ["vite/client"],
    "paths": {
      "@/*": ["src/*"],
      "utils/*": ["src/utils/*"],
      "api/*": ["src/api/*"],
      "components/*": ["src/components/*"],
      "#/*": ["types/*"],
      "build": ["build/*"]
    },
    "lib": ["ESNext", "DOM"],
    "skipLibCheck": true,
    "typeRoots": ["./node_modules/@types/", "./types"]
  },
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "build/**/*.ts", "tests/**/*.ts", "tests/**/*.tsx"],
  "exclude": ["node_modules", "tsconfig.json", "dist"]
}

带注释代码

{
  "compilerOptions": {
    // ↓指定ECMAScript目标版本,esnext为最新版本
    "target": "ESNext",
    
    // ↓将 class 声明中的字段语义从 [[Set]] 变更到 [[Define]]
    "useDefineForClassFields": true,
    
    // ↓指定生成哪个模块系统代码,esnext为最新版本
    "module": "ESNext",
    
    "importHelpers": true,
    
    // ↓决定如何处理模块。
    "moduleResolution": "Node",
    
    // ↓启用实验性的ES装饰器。
    "experimentalDecorators": true,
    
    // ↓允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。
    "allowSyntheticDefaultImports": true,
    
    // ↓启用所有严格类型检查选项。
    "strict": true,
    
    // ↓在 .tsx文件里支持JSX
    "jsx": "preserve",
    
    // ↓有未使用的局部变量抛错。
    // "noUnusedLocals": true,
    
    // ↓有未使用的参数抛错。
    // "noUnusedParameters": true,
    
     // ↓生成相应的 .map文件。
    "sourceMap": true,
    
    "resolveJsonModule": true,
    
    //将每个文件作为单独模块
    "isolatedModules": true,
    
    "esModuleInterop": true,
    
    // ↓解析非相对模块名的基准目录。
    "baseUrl": ".",
    
    // ↓要包含的类型声明文件名列表。
    "types": ["vite/client"],
    
    // ↓模块名到基于 baseUrl的路径映射的列表。
    "paths": {
      "@/*": ["src/*"],
      "utils/*": ["src/utils/*"],
      "api/*": ["src/api/*"],
      "components/*": ["src/components/*"],
      "views/*": ["src/views/*"],
      "#/*": ["types/*"],
      "build": ["build/*"]
    },
    
    // ↓编译过程中需要引入的库文件的列表。
    "lib": ["ESNext", "DOM"],
    
    // ↓忽略所有的声明文件( *.d.ts)的类型检查。
    "skipLibCheck": true,
    
    // ↓要包含的类型声明文件路径列表。
    "typeRoots": ["./node_modules/@types/", "./types"],
  },
  
  // ↓指定一个匹配列表(属于自动指定该路径下的所有ts相关文件)
  "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "build/**/*.ts", "tests/**/*.ts", "tests/**/*.tsx"],
  
  // 指定一个排除列表(include的反向操作)
  "exclude": ["node_modules", "tsconfig.json", "dist"]
}