React随机词汇替换TypeScript报错:Property 'dataset'不存在于EventTarget
解决TypeScript中
EventTarget无dataset属性的报错 问题描述
我正在通过React解决JS问题学习React,编辑器抛出类型错误:
Error: Property 'dataset' does not exist on type 'EventTarget'.(2339)
但浏览器控制台无报错,相关代码如下:
import { FC } from 'react'; import './style.css'; export const App: FC<{ name: string }> = ({ name }) => { const buttons = document.querySelectorAll('button'); const textarea = document.querySelector('textarea'); const output = document.querySelector('.output'); // flavours const flavours = { ninja: ['dojo', 'shogun', 'shinobi', 'samuri', 'shuriken'], pokemon: ['pika', 'pokeball', 'pokedex', 'evolve', 'ash', 'gym'], space: ['universe', 'galaxy', 'telescope', 'comet', 'stars'], }; // functions const flavourize = (inputText, flavour) => { const textArray = inputText.split(' '); for (let i = 0; i < textArray.length; i++) { if (i % 3 === 0) { const random = Math.floor(Math.random() * flavour.length); textArray[i] = flavour[random]; } } return textArray.join(' '); }; const updateOutput = (text) => { output.textContent = text; }; // event listener buttons.forEach((b) => { b.addEventListener('click', (e) => { e.preventDefault(); if (textarea.value) { const f = e.target.dataset.flavour; const text = flavourize(textarea.value, flavours[f]); updateOutput(text); } }); }); return ( <div> <h1>Hello {name}!</h1> <p>Start editing to see some magic happen :)</p> <form> <textarea></textarea> <div className="buttons"> <p>Choose your theme:</p> <button data-flavour="ninja">Ninja</button> <button data-flavour="pokemon">Pokemon</button> <button data-flavour="space">Space</button> </div> </form> </div> ); };
报错原因
TypeScript对EventTarget的类型定义是通用的,它不包含dataset属性——因为EventTarget可以指向任何DOM元素甚至非DOM对象。但在浏览器运行时,e.target实际是HTMLButtonElement,确实拥有dataset属性,所以运行时不会报错,只有编辑器的类型检查会提示错误。
解决方案
方案1:类型断言(快速修复)
直接将e.target断言为HTMLButtonElement,明确告诉TypeScript这个目标元素是按钮,拥有dataset属性:
// 修改事件处理中的这一行 const f = (e.target as HTMLButtonElement).dataset.flavour;
方案2:改用React事件绑定(最佳实践)
原代码使用原生DOM查询和事件监听,不符合React的声明式编程模式。推荐直接在JSX中绑定事件,并用useRef获取DOM元素,同时通过函数参数传递主题类型,完全避免类型问题:
import { FC, useRef } from 'react'; import './style.css'; export const App: FC<{ name: string }> = ({ name }) => { const textareaRef = useRef<HTMLTextAreaElement>(null); const outputRef = useRef<HTMLDivElement>(null); const flavours = { ninja: ['dojo', 'shogun', 'shinobi', 'samuri', 'shuriken'], pokemon: ['pika', 'pokeball', 'pokedex', 'evolve', 'ash', 'gym'], space: ['universe', 'galaxy', 'telescope', 'comet', 'stars'], }; const flavourize = (inputText: string, flavour: string[]) => { const textArray = inputText.split(' '); for (let i = 0; i < textArray.length; i++) { if (i % 3 === 0) { const random = Math.floor(Math.random() * flavour.length); textArray[i] = flavour[random]; } } return textArray.join(' '); }; const updateOutput = (text: string) => { if (outputRef.current) { outputRef.current.textContent = text; } }; const handleButtonClick = (flavour: keyof typeof flavours) => (e: React.FormEvent) => { e.preventDefault(); if (textareaRef.current?.value) { const text = flavourize(textareaRef.current.value, flavours[flavour]); updateOutput(text); } }; return ( <div> <h1>Hello {name}!</h1> <p>Start editing to see some magic happen :)</p> <form> <textarea ref={textareaRef}></textarea> <div className="buttons"> <p>Choose your theme:</p> <button onClick={handleButtonClick('ninja')}>Ninja</button> <button onClick={handleButtonClick('pokemon')}>Pokemon</button> <button onClick={handleButtonClick('space')}>Space</button> </div> <div className="output" ref={outputRef}></div> </form> </div> ); };
这个方案的优点:
- 遵循React声明式范式,避免直接操作DOM
- 通过函数参数传递主题,类型安全,无需依赖
dataset - 使用
useRef获取DOM元素,TypeScript能正确推断元素类型
内容的提问来源于stack exchange,提问作者zi zi
相关产品推荐
相关产品推荐

