在JavaScript项目中,JSDoc是常用的类型注释工具,当我们需要定义一个既有固定属性,又允许动态扩展其他属性的对象类型时,需要结合常规属性声明和索引签名来实现。这种场景在配置对象、动态数据存储对象等场景中非常常见。
JSDoc基础对象类型定义
首先我们来看JSDoc中定义普通固定属性对象的基础语法,通过@typedef可以自定义类型,然后用@property声明固定属性。
/**
* 基础用户对象类型
* @typedef {Object} BaseUser
* @property {number} id - 用户ID
* @property {string} name - 用户名称
* @property {number} age - 用户年龄
*/
/** @type {BaseUser} */
const user = {
id: 1,
name: '张三',
age: 20
};
添加动态扩展属性支持
如果需要让对象除了固定属性外,还能添加任意其他属性,就需要使用索引签名。JSDoc中索引签名的语法是[key: KeyType]: ValueType,表示键为KeyType类型,值为ValueType类型的任意属性。
动态属性为任意类型的情况
当动态扩展的属性值可以是任意类型时,我们可以将索引签名的值类型设为any。
/**
* 带动态扩展属性的用户对象类型
* @typedef {Object} UserWithExtra
* @property {number} id - 用户ID
* @property {string} name - 用户名称
* @property {number} age - 用户年龄
* @property {any} [key: string] - 动态扩展的任意属性
*/
/** @type {UserWithExtra} */
const user1 = {
id: 1,
name: '张三',
age: 20,
gender: '男', // 动态扩展的属性
address: '北京市', // 动态扩展的属性
score: 90 // 动态扩展的属性
};
动态属性为固定类型的情况
如果动态扩展的属性值有固定的类型要求,比如只能是字符串,只需要修改索引签名的值类型即可。
/**
* 动态扩展属性为字符串的用户对象类型
* @typedef {Object} UserWithStringExtra
* @property {number} id - 用户ID
* @property {string} name - 用户名称
* @property {string} [key: string] - 动态扩展的字符串属性
*/
/** @type {UserWithStringExtra} */
const user2 = {
id: 2,
name: '李四',
hobby: '篮球',
job: '工程师'
};
// 以下赋值会提示类型错误,因为动态属性值不是字符串
// user2.extra = 123;
注意事项
- 索引签名的键类型只能是
string或者number,不能使用其他类型作为动态属性的键。 - 固定属性的类型需要和索引签名的值类型兼容,比如如果索引签名的值是
string,那么固定属性的值也必须是字符串类型,否则会提示类型冲突。 - 如果使用TypeScript,这种写法同样适用,JSDoc的类型语法和TypeScript的类型语法在很多场景下是互通的。
实际应用场景示例
比如我们定义一个接口响应的通用结构,既有固定的code、msg字段,又允许返回任意的data字段:
/**
* 通用接口响应类型
* @typedef {Object} ApiResponse
* @property {number} code - 响应状态码
* @property {string} msg - 响应消息
* @property {any} [key: string] - 其他动态返回字段
*/
/** @type {ApiResponse} */
const response = {
code: 200,
msg: '请求成功',
data: { list: [], total: 0 }, // 动态扩展的data字段
timestamp: Date.now() // 动态扩展的时间戳字段
};
通过上述方法,我们就可以灵活地用JSDoc定义同时包含固定属性和动态扩展属性的对象类型,满足不同场景下的类型注释需求。