You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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 });
  };
}

问题原因

  1. 选择器错误:第一个输入框的装饰器使用了"input"作为选择器,这会匹配页面上所有<input>元素,而实际需要的是ID为data的输入框,应该用"#data"。
  2. 私有字段与原型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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.03 17:30:58