本文介绍一种基于 JSDoc 泛型与条件映射类型的技巧,让 TypeScript(或支持 JSDoc 类型检查的编辑器如 VS Code)自动推导代理对象中字符串属性的运行时类型,从而彻底避免在每个对象字段上重复书写 @type {Cypress.Chainable>}。
本文介绍一种基于
js
doc 泛型与条件映射类型的技巧,让 typescript(或支持 jsdoc 类型检查的编辑器如 vs code)自动推导代理对象中字符串属性的运行时类型,从而彻底避免在每个对象字段上重复书写 `@type {cypress.chainable
>}`。
在 Cypress + Page Object Model(POM)实践中,我们常借助 Proxy 将页面对象中的 CSS 选择器字符串(如 'input'、'[data-testid=logout-button]')动态包装为 cy.get(...) 链式调用对象,以提升类型安全与 IDE 补全体验。但传统写法需为每个字符串字段手动添加 JSDoc 类型注解,既冗余又易出错:
✅
解决方案:用 JSDoc 条件映射类型替代逐字段注解
核心在于升级 getPageObjectProxy 的 JSDoc 类型声明,利用 keyof T 和 T[key] extends string ? ... : ... 实现「按值类型自动映射」:
该返回类型声明表示:
遍历原对象 T 的所有键 keyof T;
对每个键 key,若其原始值类型 T[key] 是 string,则新对象中该键的类型为 Cypress.Chainable>;
否则保持原类型(如方法函数、嵌套对象等)。
使用时,页面对象可完全「零注解」定义:
php版微信js-sdk支付接口类
php版微信js-sdk支付接口类
下载
⚠️
关键注意事项
方法中避免 this
:因 Proxy 的 this 在方法内仍指向原始对象(未被代理),故方法内部须显式引用代理后的对象(如 homePage.modeDropdown),否则类型推导失效且运行时报错;
IDE 支持要求
:需启用 checkJs: true(在 jsconfig.json 或 tsconfig.json 中),并确保编辑器为 VS Code 或 WebStorm 等支持 JSDoc 条件类型的工具;
类型守卫局限性
:JSDoc 不支持运行时类型断言,因此 typeof target[prop] === 'string' 仅作逻辑判断,类型映射完全依赖 JSDoc 声明——务必保证输入对象中字符串字段确实为纯 string 字面量;
扩展性提示
:如需支持其他类型转换(如 number → cy.wrap()),可扩展条件类型:T[key] extends string ? ... : T[key] extends number ? ... : T[key]。
通过这一模式,你不仅消除了模板化注解,更将类型契约从「字段级」提升至「函数级」,使代码更简洁、可维护性更强,同时保持完整的智能提示与编译期检查能力。
export const homePage = getPageObjectProxy({
/** @type {Cypress.Chainable>} */
searchField: 'input',
/** @type {Cypress.Chainable>} */
logOutButton: '[data-testid=logout-button]',
// ……更多字段
}); /**
* @template T
* @param {T} pageObject a page object with string selectors
* @returns {{[key in keyof T]: T[key] extends string ? Cypress.Chainable> : T[key]}}
* A proxied object where string-valued properties are auto-converted to Chainable, others preserved.
*/
export const getPageObjectProxy = pageObject => new Proxy(pageObject, {
get(target, prop) {
if (typeof target[prop] === 'string') {
return cy.get(target[prop]);
}
return target[prop];
}
}); export const homePage = getPageObjectProxy({
searchField: 'input',
logOutButton: '[data-testid=logout-button]',
modeDropdown: '[data-testid=mode-dropdown]',
setMode(name) {
// ✅ 直接使用 homePage(而非 this),确保类型上下文正确
homePage.modeDropdown.click();
homePage.modeDropdown.children().contains(name).click();
}
});