Skip to content

React 事件处理

概述

React 提供了一套强大的事件处理系统,让我们能够响应用户的各种交互操作。本章将学习 React 事件的工作原理、事件处理器的编写方法、事件对象的使用,以及各种常见的用户交互场景。

🎯 React 事件系统

SyntheticEvent(合成事件)

jsx
function EventBasics() {
  const handleClick = (event) => {
    console.log('事件类型:', event.type);
    console.log('目标元素:', event.target);
    console.log('当前元素:', event.currentTarget);
    
    // 阻止默认行为
    event.preventDefault();
    
    // 阻止事件冒泡
    event.stopPropagation();
  };
  
  return (
    <div>
      <button onClick={handleClick}>点击我</button>
      <a href="#" onClick={handleClick}>
        点击链接(默认行为被阻止)
      </a>
    </div>
  );
}

事件绑定方式

jsx
// 函数组件推荐方式
function ModernEventBinding() {
  const [count, setCount] = React.useState(0);
  
  // 推荐:使用 useCallback 优化性能
  const handleClick = React.useCallback(() => {
    setCount(prev => prev + 1);
  }, []);
  
  return (
    <div>
      <p>计数: {count}</p>
      <button onClick={handleClick}>增加</button>
    </div>
  );
}

// 类组件事件绑定
class ClassEventBinding extends React.Component {
  state = { count: 0 };
  
  // 推荐:箭头函数属性
  handleClick = () => {
    this.setState(prev => ({ count: prev.count + 1 }));
  };
  
  render() {
    return (
      <div>
        <p>计数: {this.state.count}</p>
        <button onClick={this.handleClick}>增加</button>
      </div>
    );
  }
}

🖱️ 鼠标事件

基础鼠标事件

jsx
function MouseEvents() {
  const [position, setPosition] = React.useState({ x: 0, y: 0 });
  const [clicked, setClicked] = React.useState(false);
  
  const handleMouseMove = (event) => {
    setPosition({ x: event.clientX, y: event.clientY });
  };
  
  const handleClick = () => {
    setClicked(true);
    setTimeout(() => setClicked(false), 1000);
  };
  
  return (
    <div 
      onMouseMove={handleMouseMove}
      style={{ height: '200px', border: '1px solid #ccc', padding: '20px' }}
    >
      <p>鼠标位置: ({position.x}, {position.y})</p>
      <button 
        onClick={handleClick}
        style={{ backgroundColor: clicked ? 'green' : 'blue', color: 'white' }}
      >
        {clicked ? '已点击!' : '点击我'}
      </button>
    </div>
  );
}

拖拽功能

jsx
function DragAndDrop() {
  const [items, setItems] = React.useState([
    { id: 1, text: '拖拽我', x: 0, y: 0 }
  ]);
  const [dragging, setDragging] = React.useState(null);
  
  const handleMouseDown = (event, item) => {
    setDragging({
      id: item.id,
      offsetX: event.clientX - item.x,
      offsetY: event.clientY - item.y
    });
  };
  
  const handleMouseMove = (event) => {
    if (dragging) {
      setItems(items.map(item =>
        item.id === dragging.id
          ? {
              ...item,
              x: event.clientX - dragging.offsetX,
              y: event.clientY - dragging.offsetY
            }
          : item
      ));
    }
  };
  
  const handleMouseUp = () => {
    setDragging(null);
  };
  
  React.useEffect(() => {
    if (dragging) {
      document.addEventListener('mousemove', handleMouseMove);
      document.addEventListener('mouseup', handleMouseUp);
      return () => {
        document.removeEventListener('mousemove', handleMouseMove);
        document.removeEventListener('mouseup', handleMouseUp);
      };
    }
  }, [dragging]);
  
  return (
    <div style={{ position: 'relative', height: '300px', border: '1px solid #ccc' }}>
      {items.map(item => (
        <div
          key={item.id}
          onMouseDown={(e) => handleMouseDown(e, item)}
          style={{
            position: 'absolute',
            left: item.x,
            top: item.y,
            padding: '10px',
            backgroundColor: 'lightblue',
            cursor: 'move',
            userSelect: 'none'
          }}
        >
          {item.text}
        </div>
      ))}
    </div>
  );
}

⌨️ 键盘事件

键盘输入处理

jsx
function KeyboardEvents() {
  const [input, setInput] = React.useState('');
  const [history, setHistory] = React.useState([]);
  
  const handleKeyDown = (event) => {
    if (event.key === 'Enter') {
      if (input.trim()) {
        setHistory([...history, input]);
        setInput('');
      }
    } else if (event.key === 'Escape') {
      setInput('');
    }
  };
  
  return (
    <div>
      <input
        type="text"
        value={input}
        onChange={(e) => setInput(e.target.value)}
        onKeyDown={handleKeyDown}
        placeholder="输入内容,按 Enter 提交,Escape 清空"
        style={{ width: '300px', padding: '10px' }}
      />
      
      <h3>历史记录:</h3>
      <ul>
        {history.map((item, index) => (
          <li key={index}>{item}</li>
        ))}
      </ul>
    </div>
  );
}

📝 表单事件

表单控件事件

jsx
function FormEvents() {
  const [formData, setFormData] = React.useState({
    name: '',
    email: '',
    gender: '',
    agree: false
  });
  
  const handleChange = (event) => {
    const { name, value, type, checked } = event.target;
    setFormData({
      ...formData,
      [name]: type === 'checkbox' ? checked : value
    });
  };
  
  const handleSubmit = (event) => {
    event.preventDefault();
    console.log('表单数据:', formData);
    alert('表单提交成功!');
  };
  
  return (
    <form onSubmit={handleSubmit} style={{ maxWidth: '400px' }}>
      <div style={{ marginBottom: '15px' }}>
        <label>姓名:</label>
        <input
          type="text"
          name="name"
          value={formData.name}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        />
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label>邮箱:</label>
        <input
          type="email"
          name="email"
          value={formData.email}
          onChange={handleChange}
          style={{ width: '100%', padding: '8px' }}
        />
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label>性别:</label>
        <select name="gender" value={formData.gender} onChange={handleChange}>
          <option value="">请选择</option>
          <option value="male">男</option>
          <option value="female">女</option>
        </select>
      </div>
      
      <div style={{ marginBottom: '15px' }}>
        <label>
          <input
            type="checkbox"
            name="agree"
            checked={formData.agree}
            onChange={handleChange}
          />
          同意条款
        </label>
      </div>
      
      <button type="submit" disabled={!formData.agree}>
        提交
      </button>
    </form>
  );
}

⚡ 事件性能优化

防抖和节流

jsx
function EventOptimization() {
  const [searchTerm, setSearchTerm] = React.useState('');
  const [debouncedTerm, setDebouncedTerm] = React.useState('');
  
  // 防抖处理
  React.useEffect(() => {
    const timer = setTimeout(() => {
      setDebouncedTerm(searchTerm);
    }, 300);
    
    return () => clearTimeout(timer);
  }, [searchTerm]);
  
  // 节流处理
  const [scrollPosition, setScrollPosition] = React.useState(0);
  
  const throttledScroll = React.useCallback(
    React.useMemo(() => {
      let lastRun = 0;
      return (event) => {
        if (Date.now() - lastRun >= 100) {
          setScrollPosition(event.target.scrollTop);
          lastRun = Date.now();
        }
      };
    }, []),
    []
  );
  
  return (
    <div>
      <h3>搜索(防抖)</h3>
      <input
        type="text"
        value={searchTerm}
        onChange={(e) => setSearchTerm(e.target.value)}
        placeholder="输入搜索内容..."
      />
      <p>搜索词:{debouncedTerm}</p>
      
      <h3>滚动(节流)</h3>
      <div
        onScroll={throttledScroll}
        style={{ height: '100px', overflow: 'auto', border: '1px solid #ccc' }}
      >
        <div style={{ height: '300px', padding: '10px' }}>
          滚动这个区域查看位置信息
          <br />滚动位置:{scrollPosition}
        </div>
      </div>
    </div>
  );
}

🎪 事件委托

高效事件处理

jsx
function EventDelegation() {
  const [items, setItems] = React.useState([
    { id: 1, name: '项目 1', active: false },
    { id: 2, name: '项目 2', active: false },
    { id: 3, name: '项目 3', active: false }
  ]);
  
  // 事件委托处理
  const handleContainerClick = (event) => {
    const button = event.target.closest('button');
    if (button) {
      const action = button.dataset.action;
      const itemId = parseInt(button.dataset.itemId);
      
      if (action === 'toggle') {
        setItems(prev =>
          prev.map(item =>
            item.id === itemId ? { ...item, active: !item.active } : item
          )
        );
      } else if (action === 'delete') {
        setItems(prev => prev.filter(item => item.id !== itemId));
      }
    }
  };
  
  const addItem = () => {
    const newId = Math.max(...items.map(item => item.id)) + 1;
    setItems([...items, { id: newId, name: `项目 ${newId}`, active: false }]);
  };
  
  return (
    <div onClick={handleContainerClick}>
      <button onClick={addItem} style={{ marginBottom: '10px' }}>
        添加项目
      </button>
      
      {items.map(item => (
        <div
          key={item.id}
          style={{
            padding: '10px',
            margin: '5px 0',
            border: '1px solid #ddd',
            backgroundColor: item.active ? '#e7f3ff' : '#f8f9fa'
          }}
        >
          {item.name} {item.active ? '(激活)' : ''}
          <button
            data-action="toggle"
            data-item-id={item.id}
            style={{ marginLeft: '10px' }}
          >
            切换状态
          </button>
          <button
            data-action="delete"
            data-item-id={item.id}
            style={{ marginLeft: '5px', color: 'red' }}
          >
            删除
          </button>
        </div>
      ))}
    </div>
  );
}

📝 本章小结

通过本章学习,你应该掌握了:

核心概念

  • ✅ React 合成事件的工作原理
  • ✅ 事件对象的属性和方法
  • ✅ 事件绑定的最佳实践
  • ✅ 事件冒泡和默认行为控制

事件类型

  • ✅ 鼠标事件:点击、移动、拖拽
  • ✅ 键盘事件:按键处理、快捷键
  • ✅ 表单事件:输入、提交、验证
  • ✅ 触摸事件:移动端交互

性能优化

  • ✅ 事件委托减少监听器数量
  • ✅ 防抖和节流优化频繁事件
  • ✅ useCallback 缓存事件处理器
  • ✅ 合理的事件绑定时机

最佳实践

  1. 使用合成事件:兼容性好,功能完整
  2. 避免内联函数:使用 useCallback 优化
  3. 合理使用事件委托:减少内存占用
  4. 处理清理逻辑:防止内存泄漏
  5. 性能监控:关注事件处理性能

继续学习下一章 - React 条件和列表渲染

本站内容仅供学习和研究使用。