React项目中JavaScript事件监听器仅在修改保存后生效问题
问题:React项目首次启动按钮无响应,热重载后恢复正常
我在VS Code中通过npm start本地运行React项目,项目包含2个文本输入框和若干功能按钮。首次启动时项目运行正常,但按钮无响应;只要在VS Code中修改内容(哪怕加个空格)并保存,触发实时重新编译后,按钮的事件监听器就能正常工作,且可多次调用。
原代码
import logo from './logo.svg'; import './App.css'; function App() { return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p> {/* Edit <code>src/App.js</code> and save to reload. */} Search Engine </p> <p> <table> <tr> <td> Enter the token </td> <td> <input type="text" id="inputtext" placeholder='Token' /> </td> </tr> <tr> <td></td> <td> <button type="button" id="add">Add</button> <button type="button" id="display">Display</button> <button type="button" id="remove">Remove</button> <button type="button" id="">Search!</button> </td> </tr> <tr>{"----------------------------"}</tr> <tr> <td> Web link </td> <td> <input type="text" id="inputtext" placeholder='Token' /> </td> </tr> <tr> <td></td> <td> <button type="button" id="">Search!</button> </td> </tr> </table> </p> </header> </div> ); } let addButton = document.querySelector('#add'); let displayButton = document.querySelector('#display'); let removeButton = document.querySelector('#remove'); let records = []; addButton?.addEventListener('click', addRecord); displayButton?.addEventListener('click', displayAll); removeButton?.addEventListener('click', removeRecord); function addRecord() { let record = document.querySelector('#inputtext').value.trim(); if (!record) { alert('No value inserted'); return; } if (records.includes(record)) { alert('Duplicated token'); return; } records.push(record); document.querySelector('#inputtext').value = ''; } function displayAll() { if (records.length === 0) { alert("[Empty list]") return; } alert(records.sort()); } function removeRecord() { let record = document.querySelector('#inputtext').value.trim(); if (!record) { alert('No value inserted'); return; } if (records.length === 0) { alert("[Empty list]") return; } if (!records.includes(record)) { alert("Non existing value") return; } const index = records.indexOf(record); records.splice(index, 1); document.querySelector('#inputtext').value = ''; alert("Succesfully removed " + record); } export default App;
问题原因
你现在的写法是在React组件外部直接调用document.querySelector获取DOM元素并绑定事件,但React组件首次渲染时,对应的DOM还没有挂载到页面上,所以此时addButton、displayButton这些变量的值都是null,?.操作符会跳过事件绑定,导致按钮点击无响应。而热重载时,组件已经完成过一次渲染,DOM已经存在,此时重新执行外部的代码就能找到元素并绑定事件,所以按钮恢复正常。
另外还有个问题:两个输入框的id都是inputtext,这不符合HTML规范,而且后续获取值时也会混乱。
解决方案
改用React的标准写法:
- 使用
useState管理组件状态(比如records和输入框的值) - 在组件内部通过
onClick直接绑定事件处理函数,不需要手动操作DOM - 修复重复的
id问题,给两个输入框分别设置唯一标识
修改后的代码
import logo from './logo.svg'; import './App.css'; import { useState } from 'react'; function App() { // 用useState管理记录列表和两个输入框的值 const [records, setRecords] = useState([]); const [tokenInput, setTokenInput] = useState(''); const [linkInput, setLinkInput] = useState(''); // 添加记录的处理函数 const addRecord = () => { const trimmedToken = tokenInput.trim(); if (!trimmedToken) { alert('No value inserted'); return; } if (records.includes(trimmedToken)) { alert('Duplicated token'); return; } // 更新记录列表 setRecords([...records, trimmedToken]); // 清空输入框 setTokenInput(''); }; // 显示所有记录的处理函数 const displayAll = () => { if (records.length === 0) { alert("[Empty list]"); return; } alert([...records].sort()); // 排序时不修改原数组 }; // 删除记录的处理函数 const removeRecord = () => { const trimmedToken = tokenInput.trim(); if (!trimmedToken) { alert('No value inserted'); return; } if (records.length === 0) { alert("[Empty list]"); return; } if (!records.includes(trimmedToken)) { alert("Non existing value"); return; } // 过滤掉要删除的记录 setRecords(records.filter(item => item !== trimmedToken)); // 清空输入框 setTokenInput(''); alert(`Successfully removed ${trimmedToken}`); }; return ( <div className="App"> <header className="App-header"> <img src={logo} className="App-logo" alt="logo" /> <p>Search Engine</p> <p> <table> <tr> <td>Enter the token</td> <td> {/* 绑定输入框的 onChange 事件更新状态 */} <input type="text" value={tokenInput} onChange={(e) => setTokenInput(e.target.value)} placeholder='Token' /> </td> </tr> <tr> <td></td> <td> {/* 直接通过 onClick 绑定处理函数 */} <button type="button" onClick={addRecord}>Add</button> <button type="button" onClick={displayAll}>Display</button> <button type="button" onClick={removeRecord}>Remove</button> <button type="button">Search!</button> </td> </tr> <tr><td colSpan="2">----------------------------</td></tr> <tr> <td>Web link</td> <td> <input type="text" value={linkInput} onChange={(e) => setLinkInput(e.target.value)} placeholder='Web link' /> </td> </tr> <tr> <td></td> <td> <button type="button">Search!</button> </td> </tr> </table> </p> </header> </div> ); } export default App;
说明
- 用
useState管理状态是React的核心模式,避免了直接操作DOM和全局变量的问题 - 事件处理函数定义在组件内部,直接通过
onClick绑定,确保组件渲染时事件能正确关联 - 输入框通过
value和onChange实现受控组件,状态和UI保持同步 - 修复了重复
id的问题,两个输入框分别绑定不同的状态变量
内容的提问来源于stack exchange,提问作者Soto1810
相关产品推荐
相关产品推荐

