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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 09:06:57