TypeScript中domInjector装饰器注入DOM元素失败问题排查
DOM注入装饰器属性值为undefined的问题排查与解决
问题描述
开发工作日交易登记应用时,尝试用domInjector属性装饰器自动注入DOM输入元素,替代手动调用document.querySelector的方式,但控制器中被装饰的属性值均为undefined,无法获取输入框的值。
控制器代码
import { domInjector } from "../decorators/dom-injector.js"; import { inspectMethod } from "../decorators/inspect-method.js"; import { logExecutionTime } from "../decorators/log-execution-time.js"; import { WeekDays } from "../enums/week-days.js"; import { Negotiation } from "../models/negotiation.js"; import { Negotiations } from "../models/negotiations.js"; import { MessageView } from "../views/message-view.js"; import { NegotiationsView } from "../views/negotiations-view.js"; export class NegotiationController { // 错误:选择器使用不当 @domInjector("input") private _inputDate: HTMLInputElement; @domInjector("#quantidade") private _inputQuantity: HTMLInputElement; @domInjector("#valor") private _inputValue: HTMLInputElement; private _negotiations = new Negotiations(); private _negotationsView = new NegotiationsView("#table-container"); private _messageView = new MessageView("#mensagemView"); constructor() { this._negotationsView.update(this._negotiations); } @logExecutionTime(true) @inspectMethod public addNegotiation(): void { console.log(this._inputDate); // undefined console.log(this._inputQuantity); // undefined console.log(this._inputValue); // undefined const negotiation = Negotiation.createOf( this._inputDate.value, this._inputQuantity.value, this._inputValue.value ); if (!NegotiationController.isWeekDay(negotiation.date)) { this._messageView.update( "只能在工作日添加交易记录", negotiation ); return; } this._negotiations.addNewNegotiation(negotiation); this.cleanForm(); this.updateUi(negotiation); } public static isWeekDay(date: Date): boolean { return ( date.getDay() !== WeekDays.SUNDAY && date.getDay() !== WeekDays.SATURDAY ); } private cleanForm(): void { this._inputDate.value = ""; this._inputQuantity.value = ""; this._inputValue.value = ""; this._inputDate.focus(); } private updateUi(negotiation: Negotiation): void { this._negotationsView.update(this._negotiations, true); this._messageView.update("交易记录添加成功", negotiation); setTimeout(() => this._messageView.clearMesasage(), 3000); } }
装饰器代码
export function domInjector(seletor: string) { return function (target: any, propertyKey: string) { console.log(`Modificando protype ${target.constructor.name} e adicionando getter para a propriedade ${propertyKey}`); let elemento: HTMLElement; const getter = function () { if (!elemento) { elemento = <HTMLElement>document.querySelector(seletor); console.log(`buscando elemento do DOM com o seletor ${seletor} para injetar em ${propertyKey}`); } return elemento; }; Object.defineProperty(target, propertyKey, { get: getter }); }; }
问题原因
- 选择器错误:第一个输入框的装饰器使用了
"input"作为选择器,这会匹配页面上所有<input>元素,而实际需要的是ID为data的输入框,应该用"#data"。 - 私有字段与原型getter冲突:TypeScript的
private修饰符会在实例上创建一个私有字段,当访问this._inputDate时,会优先读取实例的私有字段(未初始化,值为undefined),而不会触发装饰器在原型上定义的getter方法。
解决方法
步骤1:修正选择器
将第一个装饰器的参数从"input"改为"#data",与原手动查询的目标一致:
@domInjector("#data") private _inputDate: HTMLInputElement;
步骤2:解决私有字段冲突
有两种可行方案:
方案A:使用declare声明属性(推荐)
用declare告诉TypeScript该属性存在,但不生成实例私有字段,这样访问时会触发原型上的getter:
@domInjector("#data") declare private _inputDate: HTMLInputElement; @domInjector("#quantidade") declare private _inputQuantity: HTMLInputElement; @domInjector("#valor") declare private _inputValue: HTMLInputElement;
方案B:替换private为protected或public
去掉private修饰符,改用protected(仅允许类及子类访问)或public,这样实例不会创建私有字段,访问时会走原型的getter:
@domInjector("#data") protected _inputDate: HTMLInputElement; @domInjector("#quantidade") protected _inputQuantity: HTMLInputElement; @domInjector("#valor") protected _inputValue: HTMLInputElement;
装饰器说明
原装饰器的懒加载逻辑(第一次访问时才查询DOM元素并缓存)是合理的,无需修改。修正后,当第一次访问this._inputDate时,会触发getter方法查询DOM并缓存结果,后续访问直接返回缓存的元素。
内容的提问来源于stack exchange,提问作者Arthur Rabelo
相关产品推荐
相关产品推荐

