事件处理 #

一、事件基础 #

1.1 React事件特点 #

React 事件是合成事件(SyntheticEvent),是对原生事件的跨浏览器封装。

特点 说明
驼峰命名 onClick 而非 onclick
函数引用 传递函数而非字符串
事件委托 默认委托到根节点
跨浏览器 统一的事件对象

1.2 基本用法 #

javascript
function Button() {
  const handleClick = () => {
    console.log('按钮被点击');
  };

  return <button onClick={handleClick}>点击我</button>;
}

1.3 与原生事件对比 #

javascript
// 原生HTML事件
<button onclick="handleClick()">点击</button>

// React事件
<button onClick={handleClick}>点击</button>

二、事件绑定方式 #

2.1 函数组件中的绑定 #

javascript
function Counter() {
  const [count, setCount] = useState(0);

  // 方式一:箭头函数
  const increment = () => {
    setCount(count + 1);
  };

  return (
    <div>
      <p>{count}</p>
      <button onClick={increment}>增加</button>
      <button onClick={() => setCount(count - 1)}>减少</button>
    </div>
  );
}

2.2 类组件中的绑定 #

javascript
class Counter extends Component {
  constructor(props) {
    super(props);
    this.state = { count: 0 };

    // 方式一:在构造函数中绑定
    this.increment = this.increment.bind(this);
  }

  // 方式一:需要绑定this
  increment() {
    this.setState({ count: this.state.count + 1 });
  }

  // 方式二:类字段语法(推荐)
  decrement = () => {
    this.setState({ count: this.state.count - 1 });
  };

  render() {
    return (
      <div>
        <p>{this.state.count}</p>
        <button onClick={this.increment}>增加</button>
        <button onClick={this.decrement}>减少</button>
        {/* 方式三:箭头函数 */}
        <button onClick={() => this.setState({ count: 0 })}>重置</button>
      </div>
    );
  }
}

2.3 绑定方式对比 #

方式 性能 推荐程度
构造函数绑定 可用
类字段语法 推荐
箭头函数回调 较差 简单场景可用

三、事件对象 #

3.1 访问事件对象 #

javascript
function Form() {
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('表单提交');
  };

  const handleClick = (e) => {
    console.log('事件类型:', e.type);
    console.log('目标元素:', e.target);
    console.log('当前元素:', e.currentTarget);
  };

  return (
    <form onSubmit={handleSubmit}>
      <button type="submit" onClick={handleClick}>提交</button>
    </form>
  );
}

3.2 常用事件属性 #

javascript
function MouseEvent() {
  const handleClick = (e) => {
    console.log('鼠标位置:', e.clientX, e.clientY);
    console.log('按键:', e.button);
    console.log('是否按住Ctrl:', e.ctrlKey);
  };

  return <div onClick={handleClick}>点击我</div>;
}

function KeyboardEvent() {
  const handleKeyDown = (e) => {
    console.log('按键:', e.key);
    console.log('键码:', e.keyCode);
    console.log('是否按住Shift:', e.shiftKey);
  };

  return <input onKeyDown={handleKeyDown} />;
}

3.3 阻止默认行为 #

javascript
function Link() {
  const handleClick = (e) => {
    e.preventDefault();
    console.log('链接被点击,但不会跳转');
  };

  return (
    <a href="https://example.com" onClick={handleClick}>
      点击我
    </a>
  );
}

3.4 阻止事件冒泡 #

javascript
function EventPropagation() {
  const handleParentClick = () => {
    console.log('父元素点击');
  };

  const handleChildClick = (e) => {
    e.stopPropagation();
    console.log('子元素点击');
  };

  return (
    <div onClick={handleParentClick} style={{ padding: '20px', background: '#f0f0f0' }}>
      <button onClick={handleChildClick}>子按钮</button>
    </div>
  );
}

四、传递参数 #

4.1 箭头函数方式 #

javascript
function ItemList({ items }) {
  const handleDelete = (id, event) => {
    console.log('删除:', id);
    console.log('事件:', event);
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button onClick={(e) => handleDelete(item.id, e)}>
            删除
          </button>
        </li>
      ))}
    </ul>
  );
}

4.2 bind方式 #

javascript
class ItemList extends Component {
  handleDelete(id, event) {
    console.log('删除:', id);
    console.log('事件:', event);
  }

  render() {
    const { items } = this.props;
    
    return (
      <ul>
        {items.map(item => (
          <li key={item.id}>
            {item.name}
            <button onClick={this.handleDelete.bind(this, item.id)}>
              删除
            </button>
          </li>
        ))}
      </ul>
    );
  }
}

4.3 使用data属性 #

javascript
function ItemList({ items }) {
  const handleClick = (e) => {
    const id = e.currentTarget.dataset.id;
    console.log('ID:', id);
  };

  return (
    <ul>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button data-id={item.id} onClick={handleClick}>
            点击
          </button>
        </li>
      ))}
    </ul>
  );
}

五、常见事件类型 #

5.1 表单事件 #

javascript
function FormExample() {
  const [formData, setFormData] = useState({
    username: '',
    email: '',
    message: ''
  });

  const handleChange = (e) => {
    const { name, value, type, checked } = e.target;
    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('提交数据:', formData);
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label>用户名:</label>
        <input
          name="username"
          value={formData.username}
          onChange={handleChange}
        />
      </div>
      
      <div>
        <label>邮箱:</label>
        <input
          name="email"
          type="email"
          value={formData.email}
          onChange={handleChange}
        />
      </div>
      
      <div>
        <label>消息:</label>
        <textarea
          name="message"
          value={formData.message}
          onChange={handleChange}
        />
      </div>
      
      <button type="submit">提交</button>
    </form>
  );
}

5.2 鼠标事件 #

javascript
function MouseEvents() {
  const [position, setPosition] = useState({ x: 0, y: 0 });

  const handleMouseMove = (e) => {
    setPosition({ x: e.clientX, y: e.clientY });
  };

  const handleMouseEnter = () => {
    console.log('鼠标进入');
  };

  const handleMouseLeave = () => {
    console.log('鼠标离开');
  };

  return (
    <div
      onMouseMove={handleMouseMove}
      onMouseEnter={handleMouseEnter}
      onMouseLeave={handleMouseLeave}
      style={{ width: '300px', height: '200px', background: '#f0f0f0' }}
    >
      <p>X: {position.x}, Y: {position.y}</p>
    </div>
  );
}

5.3 键盘事件 #

javascript
function KeyboardEvents() {
  const [key, setKey] = useState('');

  const handleKeyDown = (e) => {
    console.log('按下:', e.key);
    
    if (e.key === 'Enter') {
      console.log('回车键');
    }
    if (e.key === 'Escape') {
      console.log('ESC键');
    }
    if (e.ctrlKey && e.key === 's') {
      e.preventDefault();
      console.log('Ctrl+S 保存');
    }
  };

  const handleKeyUp = (e) => {
    setKey(e.key);
  };

  return (
    <div>
      <input
        onKeyDown={handleKeyDown}
        onKeyUp={handleKeyUp}
        placeholder="按下键盘"
      />
      <p>最后释放的键: {key}</p>
    </div>
  );
}

5.4 焦点事件 #

javascript
function FocusEvents() {
  const [focused, setFocused] = useState(false);

  return (
    <input
      onFocus={() => setFocused(true)}
      onBlur={() => setFocused(false)}
      style={{ border: focused ? '2px solid blue' : '1px solid gray' }}
      placeholder={focused ? '已聚焦' : '点击聚焦'}
    />
  );
}

5.5 剪贴板事件 #

javascript
function ClipboardEvents() {
  const handleCopy = (e) => {
    console.log('复制');
  };

  const handlePaste = (e) => {
    const text = e.clipboardData.getData('text');
    console.log('粘贴:', text);
  };

  return (
    <input
      onCopy={handleCopy}
      onPaste={handlePaste}
      placeholder="尝试复制粘贴"
    />
  );
}

六、事件委托 #

6.1 原理 #

React 使用事件委托,将事件绑定到根节点:

javascript
function List() {
  const handleClick = (e) => {
    const target = e.target;
    
    if (target.tagName === 'BUTTON') {
      const action = target.dataset.action;
      const id = target.dataset.id;
      
      switch (action) {
        case 'edit':
          console.log('编辑:', id);
          break;
        case 'delete':
          console.log('删除:', id);
          break;
      }
    }
  };

  return (
    <ul onClick={handleClick}>
      {items.map(item => (
        <li key={item.id}>
          {item.name}
          <button data-action="edit" data-id={item.id}>编辑</button>
          <button data-action="delete" data-id={item.id}>删除</button>
        </li>
      ))}
    </ul>
  );
}

6.2 React 17+的变化 #

React 17 之前,事件委托到 document;React 17+,事件委托到根容器。

javascript
// React 17+
const root = createRoot(document.getElementById('root'));
root.render(<App />);
// 事件绑定到 #root 元素

七、合成事件 #

7.1 什么是合成事件 #

合成事件是 React 对原生事件的封装:

javascript
function SyntheticEventDemo() {
  const handleClick = (e) => {
    console.log('合成事件:', e);
    console.log('原生事件:', e.nativeEvent);
    console.log('事件类型:', e.type);
    console.log('目标元素:', e.target);
  };

  return <button onClick={handleClick}>点击</button>;
}

7.2 事件池 #

React 17 之前,合成事件会被池化复用:

javascript
// React 16及之前
function handleClick(e) {
  // e.persist(); // 需要调用persist才能异步访问
  setTimeout(() => {
    console.log(e.type); // null,事件已被回收
  }, 0);
}

// React 17+,不再池化
function handleClick(e) {
  setTimeout(() => {
    console.log(e.type); // 正常访问
  }, 0);
}

八、最佳实践 #

8.1 避免内联函数(性能敏感场景) #

javascript
// ❌ 每次渲染创建新函数
function List({ items }) {
  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => console.log(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

// ✅ 使用useCallback
function List({ items }) {
  const handleClick = useCallback((id) => {
    console.log(id);
  }, []);

  return (
    <ul>
      {items.map(item => (
        <li key={item.id} onClick={() => handleClick(item.id)}>
          {item.name}
        </li>
      ))}
    </ul>
  );
}

8.2 使用事件处理函数命名约定 #

javascript
// ✅ 好的命名
function Form() {
  const handleInputChange = (e) => { };
  const handleSubmit = (e) => { };
  const handleReset = () => { };
  const handleButtonClick = () => { };

  return <form onSubmit={handleSubmit}>...</form>;
}

8.3 防抖和节流 #

javascript
import { useState, useCallback } from 'react';
import { debounce, throttle } from 'lodash';

function SearchInput() {
  const [results, setResults] = useState([]);

  // 防抖搜索
  const handleSearch = useCallback(
    debounce((query) => {
      fetchResults(query).then(setResults);
    }, 300),
    []
  );

  // 节流滚动
  const handleScroll = useCallback(
    throttle(() => {
      console.log('滚动位置:', window.scrollY);
    }, 100),
    []
  );

  return (
    <div onScroll={handleScroll}>
      <input onChange={(e) => handleSearch(e.target.value)} />
      <ul>
        {results.map(result => (
          <li key={result.id}>{result.name}</li>
        ))}
      </ul>
    </div>
  );
}

8.4 键盘可访问性 #

javascript
function Button({ onClick, children }) {
  const handleKeyDown = (e) => {
    if (e.key === 'Enter' || e.key === ' ') {
      e.preventDefault();
      onClick();
    }
  };

  return (
    <div
      role="button"
      tabIndex={0}
      onClick={onClick}
      onKeyDown={handleKeyDown}
    >
      {children}
    </div>
  );
}

九、常见问题 #

9.1 this绑定问题 #

javascript
// ❌ 类组件中this丢失
class Button extends Component {
  handleClick() {
    console.log(this); // undefined
  }
  render() {
    return <button onClick={this.handleClick}>点击</button>;
  }
}

// ✅ 解决方案
class Button extends Component {
  handleClick = () => {
    console.log(this); // Button实例
  };
  render() {
    return <button onClick={this.handleClick}>点击</button>;
  }
}

9.2 事件对象异步访问 #

javascript
// React 16及之前
function handleClick(e) {
  e.persist(); // 保持事件对象
  setTimeout(() => {
    console.log(e.target); // 可以访问
  }, 0);
}

// React 17+,不需要persist
function handleClick(e) {
  setTimeout(() => {
    console.log(e.target); // 可以直接访问
  }, 0);
}

十、总结 #

要点 说明
驼峰命名 onClick, onSubmit
函数引用 传递函数而非字符串
事件对象 合成事件,跨浏览器兼容
参数传递 箭头函数或bind

核心原则:

  • 使用驼峰命名事件
  • 类组件注意 this 绑定
  • 使用 e.preventDefault() 阻止默认行为
  • 使用 e.stopPropagation() 阻止冒泡
  • 性能敏感场景使用 useCallback
最后更新:2026-03-26