类型提示概述
GitLab 项目的前端代码库目前不要求也不强制使用类型。添加类型注解是可选的,我们目前不在 JavaScript 代码库中强制执行任何类型安全。然而,类型注解可能对提高代码库的清晰度非常有帮助,尤其是在共享工具代码中。本文档旨在介绍类型提示当前的工作原理、如何添加新的类型注解,以及如何在 GitLab 项目中设置类型提示。
JSDoc
JSDoc 是一个用于记录和描述 JavaScript 代码中类型的工具,它使用特殊格式的注释。JSDoc 的类型词汇相对有限,但被许多 IDE 广泛支持 by many IDEs。
示例
描述函数
/**
* Adds two numbers
* @param {number} a first number
* @param {number} b second number
* @returns {number} sum of two numbers
*/
function add(a, b) {
return a + b;
}可选参数
在参数名称周围使用方括号 [] 将其标记为可选。可以使用 [name=value] 语法提供默认值:
/**
* Adds two numbers
* @param {number} value
* @param {number} [increment=1] optional param
* @returns {number} sum of two numbers
*/
function increment(a, b=1) {
return a + b;
}对象参数
接受对象的函数可以通过在 @param 名称中使用 object.field 表示法来添加类型:
/**
* Adds two numbers
* @param {object} config
* @param {string} config.path path
* @param {string} [config.anchor] anchor
* @returns {string}
*/
function createUrl(config) {
if (config.anchor) {
return path + '#' + anchor;
}
return path;
}为未立即赋值的变量添加类型注解
对于工具和 IDE 来说,很难推断未立即接收值的值的类型。我们可以使用 @type 表示法为这样的变量分配类型:
/** @type {number} */
let value;更多语法细节请参考 JSDoc 官方网站。
JSDoc 使用技巧
基本类型使用小写名称
大写和小写都可以接受,但在大多数情况下,基本类型或对象使用小写:boolean、number、string、symbol 或 object。
/**
* Translates `text`.
* @param {string} text - The text to be translated
* @returns {string} The translated text
*/
const gettext = (text) => locale.gettext(ensureSingleLine(text));使用知名类型
知名类型,如 HTMLDivElement 或 Intl 是可用的,可以直接使用:
/** @type {HTMLDivElement} */
let element;/**
* Creates an instance of Intl.DateTimeFormat for the current locale.
* @param {Intl.DateTimeFormatOptions} [formatOptions] - for available options, please see https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DateTimeFormat
* @returns {Intl.DateTimeFormat}
*/
const createDateTimeFormat = (formatOptions) =>
Intl.DateTimeFormat(getPreferredLocales(), formatOptions);通过 import('path/to/module') 导入现有类型定义
以下是如何为未立即定义的 Vue Test Utils Wrapper 变量添加类型注解的示例:
/** @type {import('helpers/vue_test_utils_helper').ExtendedWrapper} */
let wrapper;
// ...
wrapper = mountExtended(/* ... */);/** @type {import('@vue/test-utils').Wrapper} */
let wrapper;
// ...
wrapper = shallowMount(/* ... */);import() 不是原生 JSDoc 构造,但被许多 IDE 和工具识别。在这种情况下,我们旨在提高代码的清晰度并改善 IDE 的开发者体验。
JSDoc 的局限性
如上所述,JSDoc 的词汇有限。使用它无法完整描述类型。但有时可以使用第三方库的类型定义来使类型推断适用于我们的代码。以下是这种方法的示例:
- export const mountExtended = (...args) => extendedWrapper(mount(...args));
+ import { compose } from 'lodash/fp';
+ export const mountExtended = compose(extendedWrapper, mount);
在这里,我们使用 compose 函数的 TypeScript 类型定义,为 mountExtended 函数添加推断的类型定义。在这种情况下,mountExtended 的参数将与 mount 的参数类型相同,并且返回类型将与 extendedWrapper 的返回类型相同。
我们仍然可以使用 JSDoc 的语法为函数添加描述,例如:
/** Mounts a component and returns an extended wrapper for it */
export const mountExtended = compose(extendedWrapper, mount);系统要求
可能需要设置,以便 GitLab 代码库和第三方包的类型定义能够在 IDE 和工具中正确显示。
VS Code 设置
如果您在使用 VS Code IntelliSense 时遇到问题,可能需要增加 TS 服务器允许使用的内存量。为此,请将以下内容添加到您的 settings.json 文件中:
{
"typescript.tsserver.maxTsServerMemory": 8192,
"typescript.tsserver.nodePath": "node"
}别名
我们的代码库对导入使用许多别名。例如,import Api from '~/api'; 将导入 app/assets/javascripts/api.js 文件。但 IDE 可能不知道该别名,因此可能不知道 Api 的类型。为了解决这个问题,我们需要创建一个 jsconfig.json 文件。
GitLab 项目中有一个脚本可以根据 webpack 配置和当前环境变量生成 jsconfig.json 文件。要生成或更新 jsconfig.json 文件,请从 GitLab 项目根目录运行:
node scripts/frontend/create_jsconfig.jsjsconfig.json 已添加到 gitignore 列表中,因此创建或更改它不会导致 GitLab 项目中的 Git 变更。这也意味着它不包含在 Git 拉取中,因此必须手动生成或更新。
第三方 TypeScript 定义
虽然越来越多的库使用 TypeScript 进行类型定义,但一些库可能仍然有 JSDoc 注解的类型或根本没有类型。为了弥补这一差距,TypeScript 社区启动了 DefinitelyTyped 计划,该计划创建并支持流行 JavaScript 库的独立类型定义。我们可以通过显式安装类型包(yarn add -D "@types/lodash")或使用称为 Automatic Type Acquisition (ATA) 的功能来使用这些定义,该功能在某些语言服务中可用(例如,VS Code 中的 ATA)。
自动类型获取 (ATA) 会自动从 DefinitelyTyped 列表中获取类型定义。但要使 ATA 工作,可能需要全局安装的 npm。IDE 可以提供回退配置选项来设置 npm 可执行文件的位置。有关详细信息,请参阅您的 IDE 文档。
ATA 不能保证正常工作,而且 Lodash 是我们许多工具函数的核心,因此我们在 package.json 的 devDependencies 中明确添加了 Lodash 的 DefinitelyTyped 定义。这确保了每个人都能开箱即用地获得基于 lodash 的函数的类型提示。