Sửa lỗi TypeScript: Property 'value' does not exist on type 'EventTarget'

beginner🔵 TypeScript2026-04-21| TypeScript 4.x / 5.x, mọi dự án chạy trên trình duyệt (React, Vue, Vanilla TS, Angular), Node.js với DOM lib

Error Message

Property 'value' does not exist on type 'EventTarget'.
#typescript#dom#xử lý sự kiện

Lỗi gặp phải

Gắn một event listener, thử đọc event.target.value, và TypeScript lập tức phản ứng với:

input.addEventListener('change', (event) => {
  console.log(event.target.value); // ❌ Property 'value' does not exist on type 'EventTarget'.
});

React cũng gặp vấn đề tương tự:

const handleChange = (event: React.ChangeEvent) => {
  console.log(event.target.value); // ❌ same error
};

Nguyên nhân

event.target được định kiểu là EventTarget — interface DOM chung nhất. Nó chỉ đảm bảo một vài phương thức: addEventListener, dispatchEvent, removeEventListener. Không có gì liên quan đến value, checked, files, hay bất kỳ thuộc tính nào đặc trưng cho từng phần tử.

TypeScript sẽ không tự động thu hẹp EventTarget thành HTMLInputElement. Ở cấp độ kiểu dữ liệu, một event có thể kích hoạt trên bất kỳ phần tử nào — một <div>, một <span>, hay một node trong shadow DOM. Bạn phải nói cho trình biên dịch biết bạn thực sự đang làm việc với cái gì.

Cách sửa 1 — Type assertion (nhanh nhất)

Ép kiểu event.target sang kiểu phần tử bạn mong đợi:

input.addEventListener('change', (event) => {
  const target = event.target as HTMLInputElement;
  console.log(target.value); // ✅
});

Chọn interface phù hợp với phần tử:

  • HTMLInputElement<input>
  • HTMLTextAreaElement<textarea>
  • HTMLSelectElement<select>
  • HTMLButtonElement<button>

React có cách xử lý gọn hơn. Truyền kiểu phần tử làm tham số generic vào kiểu event và bỏ hoàn toàn việc ép kiểu:

// React — tham số generic xử lý việc định kiểu
const handleChange = (event: React.ChangeEvent<HTMLInputElement>) => {
  console.log(event.target.value); // ✅ không cần ép kiểu
};

<input type="text" onChange={handleChange} />

Cách sửa 2 — Dùng currentTarget thay vì target

Có một cách sửa tinh tế hơn giúp tránh hoàn toàn việc ép kiểu. currentTarget là phần tử mà listener của bạn được gắn vào. TypeScript suy ra kiểu của nó từ phần tử bạn gọi addEventListener — nhờ đó bạn có kiểu dữ liệu chính xác mà không cần làm gì thêm.

const input = document.querySelector('input'); // HTMLInputElement | null

input?.addEventListener('change', (event) => {
  // event.currentTarget là HTMLInputElement — không cần ép kiểu
  console.log(event.currentTarget?.value); // ✅
});

Lý do sự phân biệt này quan trọng: target là bất cứ thứ gì người dùng thực sự click hoặc nhập vào — có thể là một phần tử con lồng bên trong container của bạn. currentTarget luôn là phần tử đang giữ listener. Với các trường form, currentTarget hầu như luôn là lựa chọn đúng.

Cách sửa 3 — Type guard (an toàn nhất cho handler dùng chung)

Một handler xử lý nhiều kiểu phần tử? Dùng guard instanceof. Nó vừa thu hẹp kiểu dữ liệu vừa bảo vệ bạn tại thời điểm chạy:

function handleEvent(event: Event) {
  if (event.target instanceof HTMLInputElement) {
    console.log(event.target.value); // ✅ TypeScript biết kiểu ở đây
  } else if (event.target instanceof HTMLSelectElement) {
    console.log(event.target.value); // ✅
  }
}

document.querySelector('form')?.addEventListener('change', handleEvent);

Nếu target hóa ra là một button hay một phần tử bất ngờ nào đó bên trong form, guard sẽ bắt gọn tình huống đó thay vì để chương trình crash lúc chạy.

Cách sửa 4 — Helper handler có định kiểu (pattern có thể tái sử dụng)

Viết đi viết lại cùng một đoạn ép kiểu ở hàng chục file rất mệt mỏi. Tách ra một lần và dùng lại ở mọi nơi:

function inputHandler(fn: (value: string, event: Event) => void) {
  return (event: Event) => {
    if (event.target instanceof HTMLInputElement) {
      fn(event.target.value, event);
    }
  };
}

// Sử dụng
document.getElementById('search')?.addEventListener(
  'input',
  inputHandler((value) => console.log('search:', value))
);

Kiểm tra sau khi sửa

  • Đường gạch đỏ trong editor biến mất ngay khi bạn áp dụng ép kiểu hoặc tham số generic.
  • Chạy tsc --noEmit — không còn lỗi nào cho file đó.
  • Di chuột qua target trong IDE. Tooltip phải hiển thị HTMLInputElement (hoặc kiểu cụ thể bạn đã khai báo), không phải EventTarget chung chung.
  • Mở DevTools trên trình duyệt và xác nhận giá trị được log đúng khi chạy thực tế.

Chọn cách sửa nào

  • Handler đơn lẻ, nhanh gọn: type assertion (as HTMLInputElement)
  • Form trong React: kiểu event có generic (React.ChangeEvent<HTMLInputElement>)
  • Handler trên phần tử đã biết trước: dùng currentTarget — không cần ép kiểu
  • Handler dùng chung / đa hình: guard instanceof

Phòng tránh

Bật chế độ strict trong tsconfig.json nếu bạn chưa làm. Nó bắt được cả lớp lỗi này sớm thay vì để chúng xuất hiện lúc chạy:

{
  "compilerOptions": {
    "strict": true,
    "lib": ["DOM", "DOM.Iterable", "ESNext"]
  }
}

Kiểm tra kỹ rằng "DOM" có trong mảng lib của bạn. Thiếu nó và TypeScript sẽ không nhận ra HTMLInputElement — bạn sẽ gặp một lỗi khác, khó hiểu hơn và khiến bạn đi lạc theo hướng sai.

Một thói quen đáng xây dựng: khai báo tường minh kiểu cho tham số event handler thay vì dựa vào suy luận từ addEventListener. Kiểu bạn khai báo càng chặt chẽ từ đầu, trình biên dịch càng phát hiện lỗi sớm — trước khi bất cứ thứ gì đến được trình duyệt.

Related Error Notes