事件处理 #

一、事件基础 #

1.1 Preact 事件特点 #

Preact 使用原生 DOM 事件,而非 React 的合成事件:

特点 Preact React
事件系统 原生事件 合成事件
事件对象 原生 Event SyntheticEvent
性能 更快 略慢
兼容性 标准行为 跨浏览器统一

1.2 事件命名 #

使用驼峰命名法:

jsx
// Preact 事件命名
<button onClick={handleClick}>Click</button>
<input onInput={handleInput} />
<form onSubmit={handleSubmit} />
<input onKeyDown={handleKeyDown} />

1.3 常用事件 #

类型 事件
鼠标 onClick, onDoubleClick, onMouseEnter, onMouseLeave
键盘 onKeyDown, onKeyUp, onKeyPress
表单 onInput, onChange, onSubmit, onFocus, onBlur
触摸 onTouchStart, onTouchMove, onTouchEnd

二、基本用法 #

2.1 直接定义 #

jsx
function Button() {
  return (
    <button onClick={() => console.log('Clicked!')}>
      Click me
    </button>
  );
}

2.2 函数引用 #

jsx
function Button() {
  const handleClick = () => {
    console.log('Button clicked');
  };

  return <button onClick={handleClick}>Click me</button>;
}

2.3 事件对象 #

jsx
function Form() {
  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form submitted');
  };

  const handleInput = (e) => {
    console.log('Input value:', e.target.value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input onInput={handleInput} />
      <button type="submit">Submit</button>
    </form>
  );
}

三、传递参数 #

3.1 箭头函数 #

jsx
function ItemList({ items }) {
  const handleDelete = (id) => {
    console.log('Delete:', id);
  };

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

3.2 bind 方法 #

jsx
class ItemList extends Component {
  handleDelete = (id, e) => {
    console.log('Delete:', id);
    console.log('Event:', e);
  };

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

3.3 数据属性 #

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

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

四、表单事件 #

4.1 受控组件 #

jsx
function ControlledForm() {
  const [value, setValue] = useState('');

  const handleInput = (e) => {
    setValue(e.target.value);
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Submitted:', value);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input 
        value={value}
        onInput={handleInput}
        placeholder="Type something"
      />
      <button type="submit">Submit</button>
    </form>
  );
}

4.2 多个输入 #

jsx
function MultiInputForm() {
  const [form, setForm] = useState({
    username: '',
    email: '',
    password: ''
  });

  const handleChange = (e) => {
    const { name, value } = e.target;
    setForm(prev => ({
      ...prev,
      [name]: value
    }));
  };

  const handleSubmit = (e) => {
    e.preventDefault();
    console.log('Form data:', form);
  };

  return (
    <form onSubmit={handleSubmit}>
      <input
        name="username"
        value={form.username}
        onInput={handleChange}
        placeholder="Username"
      />
      <input
        name="email"
        type="email"
        value={form.email}
        onInput={handleChange}
        placeholder="Email"
      />
      <input
        name="password"
        type="password"
        value={form.password}
        onInput={handleChange}
        placeholder="Password"
      />
      <button type="submit">Register</button>
    </form>
  );
}

4.3 复选框和单选框 #

jsx
function CheckboxExample() {
  const [preferences, setPreferences] = useState({
    newsletter: false,
    notifications: true,
    darkMode: false
  });

  const handleCheckbox = (e) => {
    const { name, checked } = e.target;
    setPreferences(prev => ({
      ...prev,
      [name]: checked
    }));
  };

  return (
    <div>
      <label>
        <input
          type="checkbox"
          name="newsletter"
          checked={preferences.newsletter}
          onChange={handleCheckbox}
        />
        Subscribe to newsletter
      </label>
      
      <label>
        <input
          type="checkbox"
          name="notifications"
          checked={preferences.notifications}
          onChange={handleCheckbox}
        />
        Enable notifications
      </label>
    </div>
  );
}

function RadioExample() {
  const [color, setColor] = useState('red');

  return (
    <div>
      <label>
        <input
          type="radio"
          name="color"
          value="red"
          checked={color === 'red'}
          onChange={(e) => setColor(e.target.value)}
        />
        Red
      </label>
      
      <label>
        <input
          type="radio"
          name="color"
          value="blue"
          checked={color === 'blue'}
          onChange={(e) => setColor(e.target.value)}
        />
        Blue
      </label>
    </div>
  );
}

4.4 选择框 #

jsx
function SelectExample() {
  const [selected, setSelected] = useState('option1');

  return (
    <select 
      value={selected}
      onChange={(e) => setSelected(e.target.value)}
    >
      <option value="option1">Option 1</option>
      <option value="option2">Option 2</option>
      <option value="option3">Option 3</option>
    </select>
  );
}

function MultiSelectExample() {
  const [selected, setSelected] = useState(['option1']);

  const handleChange = (e) => {
    const values = Array.from(
      e.target.selectedOptions,
      option => option.value
    );
    setSelected(values);
  };

  return (
    <select multiple value={selected} onChange={handleChange}>
      <option value="option1">Option 1</option>
      <option value="option2">Option 2</option>
      <option value="option3">Option 3</option>
    </select>
  );
}

五、键盘事件 #

5.1 基本用法 #

jsx
function SearchInput() {
  const [query, setQuery] = useState('');

  const handleKeyDown = (e) => {
    if (e.key === 'Enter') {
      e.preventDefault();
      console.log('Search:', query);
    } else if (e.key === 'Escape') {
      setQuery('');
    }
  };

  return (
    <input
      value={query}
      onInput={(e) => setQuery(e.target.value)}
      onKeyDown={handleKeyDown}
      placeholder="Press Enter to search"
    />
  );
}

5.2 快捷键 #

jsx
function ShortcutExample() {
  const handleKeyDown = (e) => {
    // Ctrl/Cmd + S
    if ((e.ctrlKey || e.metaKey) && e.key === 's') {
      e.preventDefault();
      console.log('Save triggered');
    }
    
    // Ctrl/Cmd + Enter
    if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
      e.preventDefault();
      console.log('Submit triggered');
    }
  };

  return (
    <div tabIndex={0} onKeyDown={handleKeyDown}>
      <p>Press Ctrl+S to save</p>
      <p>Press Ctrl+Enter to submit</p>
    </div>
  );
}

5.3 键码对照 #

jsx
const keys = {
  Enter: 'Enter',
  Escape: 'Escape',
  Tab: 'Tab',
  ArrowUp: 'ArrowUp',
  ArrowDown: 'ArrowDown',
  ArrowLeft: 'ArrowLeft',
  ArrowRight: 'ArrowRight',
  Backspace: 'Backspace',
  Delete: 'Delete',
  Space: ' '
};

function handleKeyDown(e) {
  switch (e.key) {
    case 'Enter':
      // 处理回车
      break;
    case 'Escape':
      // 处理退出
      break;
    case 'ArrowUp':
      // 处理上箭头
      break;
  }
}

六、鼠标事件 #

6.1 基本用法 #

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

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

  return (
    <div 
      style={{ height: '300px', border: '1px solid #ccc' }}
      onMouseMove={handleMouseMove}
    >
      <p>Mouse position: {position.x}, {position.y}</p>
    </div>
  );
}

6.2 悬停效果 #

jsx
function HoverCard({ children }) {
  const [isHovered, setIsHovered] = useState(false);

  return (
    <div
      style={{
        padding: '20px',
        backgroundColor: isHovered ? '#f0f0f0' : '#fff',
        transition: 'background-color 0.2s'
      }}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      {children}
    </div>
  );
}

6.3 拖拽 #

jsx
function Draggable() {
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const [dragging, setDragging] = useState(false);
  const [offset, setOffset] = useState({ x: 0, y: 0 });

  const handleMouseDown = (e) => {
    setDragging(true);
    setOffset({
      x: e.clientX - position.x,
      y: e.clientY - position.y
    });
  };

  const handleMouseMove = (e) => {
    if (!dragging) return;
    setPosition({
      x: e.clientX - offset.x,
      y: e.clientY - offset.y
    });
  };

  const handleMouseUp = () => {
    setDragging(false);
  };

  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: 'absolute',
        left: position.x,
        top: position.y,
        cursor: dragging ? 'grabbing' : 'grab',
        userSelect: 'none'
      }}
      onMouseDown={handleMouseDown}
    >
      Drag me
    </div>
  );
}

七、事件委托 #

7.1 基本概念 #

jsx
function List({ items }) {
  const handleClick = (e) => {
    const target = e.target.closest('li');
    if (target) {
      const id = target.dataset.id;
      console.log('Clicked item:', id);
    }
  };

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

7.2 区分目标 #

jsx
function ActionList({ items }) {
  const handleClick = (e) => {
    const button = e.target.closest('button');
    if (!button) return;

    const action = button.dataset.action;
    const id = button.closest('li').dataset.id;

    switch (action) {
      case 'delete':
        console.log('Delete:', id);
        break;
      case 'edit':
        console.log('Edit:', id);
        break;
      case 'view':
        console.log('View:', id);
        break;
    }
  };

  return (
    <ul onClick={handleClick}>
      {items.map(item => (
        <li key={item.id} data-id={item.id}>
          {item.name}
          <button data-action="view">View</button>
          <button data-action="edit">Edit</button>
          <button data-action="delete">Delete</button>
        </li>
      ))}
    </ul>
  );
}

八、自定义事件 #

8.1 创建自定义事件 #

jsx
function CustomEventExample() {
  const dispatchCustomEvent = () => {
    const event = new CustomEvent('myCustomEvent', {
      detail: { message: 'Hello from custom event' }
    });
    document.dispatchEvent(event);
  };

  useEffect(() => {
    const handleCustomEvent = (e) => {
      console.log('Custom event received:', e.detail);
    };

    document.addEventListener('myCustomEvent', handleCustomEvent);
    return () => {
      document.removeEventListener('myCustomEvent', handleCustomEvent);
    };
  }, []);

  return (
    <button onClick={dispatchCustomEvent}>
      Dispatch Custom Event
    </button>
  );
}

8.2 组件间通信 #

jsx
const EventBus = {
  listeners: {},
  
  on(event, callback) {
    if (!this.listeners[event]) {
      this.listeners[event] = [];
    }
    this.listeners[event].push(callback);
  },
  
  off(event, callback) {
    if (!this.listeners[event]) return;
    this.listeners[event] = this.listeners[event].filter(cb => cb !== callback);
  },
  
  emit(event, data) {
    if (!this.listeners[event]) return;
    this.listeners[event].forEach(callback => callback(data));
  }
};

function Publisher() {
  const handleClick = () => {
    EventBus.emit('message', { text: 'Hello from Publisher' });
  };

  return <button onClick={handleClick}>Send Message</button>;
}

function Subscriber() {
  const [message, setMessage] = useState('');

  useEffect(() => {
    const handleMessage = (data) => {
      setMessage(data.text);
    };

    EventBus.on('message', handleMessage);
    return () => EventBus.off('message', handleMessage);
  }, []);

  return <p>Received: {message}</p>;
}

九、最佳实践 #

9.1 防抖和节流 #

jsx
// 防抖 Hook
function useDebounce(callback, delay) {
  const timeoutRef = useRef(null);

  return (...args) => {
    clearTimeout(timeoutRef.current);
    timeoutRef.current = setTimeout(() => {
      callback(...args);
    }, delay);
  };
}

// 使用
function SearchInput() {
  const [query, setQuery] = useState('');

  const debouncedSearch = useDebounce((value) => {
    console.log('Searching:', value);
  }, 300);

  const handleInput = (e) => {
    const value = e.target.value;
    setQuery(value);
    debouncedSearch(value);
  };

  return <input value={query} onInput={handleInput} />;
}

9.2 阻止默认行为 #

jsx
function Link({ href, children }) {
  const handleClick = (e) => {
    e.preventDefault();
    console.log('Navigating to:', href);
  };

  return (
    <a href={href} onClick={handleClick}>
      {children}
    </a>
  );
}

9.3 停止冒泡 #

jsx
function Modal({ onClose, children }) {
  const handleBackdropClick = () => {
    onClose();
  };

  const handleContentClick = (e) => {
    e.stopPropagation();
  };

  return (
    <div class="modal-backdrop" onClick={handleBackdropClick}>
      <div class="modal-content" onClick={handleContentClick}>
        {children}
      </div>
    </div>
  );
}

十、总结 #

要点 说明
命名 驼峰命名 onClick
参数 箭头函数或 bind
表单 受控组件模式
键盘 e.key 判断按键
委托 利用事件冒泡
清理 移除事件监听器

核心原则:

  • 使用受控组件处理表单
  • 合理使用事件委托
  • 记得清理副作用
  • 防抖节流优化性能
最后更新:2026-03-28