---
date: 2026-05-18
type: error
tags: [react, typescript, frontend, event-handler]
---

# React `onClick={handler}` 把 MouseEvent 传进可选参数，污染 default fallback

## 现象

`handleSend(override?: string)` 函数体内：

```ts
const handleSend = async (override?: string) => {
  const prompt = (override ?? composerValue).trim();  // 💥 TypeError
  ...
};
```

绑到按钮：

```tsx
<button onClick={onSend} />   // 看着没问题
```

运行时报：
```
TypeError: (override ?? composerValue).trim is not a function
    at handleSend (...)
    at executeDispatch (react-dom)
```

类似形式遍布键盘事件 / window listener：`onSomething(callback)` 模式遇到"可选首参数 + 默认 fallback"必踩。

## 根因

React 把合成事件作为**第一个参数**调用 onClick handler：

```ts
button.onClick(e: SyntheticMouseEvent)
```

当 handler 签名是 `handleSend(override?: string)` 时：
- `override = SyntheticMouseEvent`（不是 undefined）
- `override ?? composerValue` 走 `override` 分支（事件对象**真值**）
- 调 `event.trim()` → 没这方法 → TypeError

**TypeScript 在调用点不查参数类型兼容**——`onClick={handler}` 的类型是
`(e: MouseEvent) => void`，handler 是 `(override?: string) => Promise<void>`，
后者 assignable to 前者（参数 contravariance：宽 ↔ 窄 对函数类型是被允许的方向）。
编译器不会报错，运行时炸。

## 解法

**永远用 arrow 包一层吃掉 event**：

```tsx
<button onClick={() => onSend()} />          // ✅ 显式不传参
<button onClick={() => onSend(undefined)} /> // 等价但啰嗦
<button onClick={() => void onSend()} />     // async 返 Promise 时配合
```

或在 handler 入口防御：

```ts
const handleSend = async (override?: string) => {
  // 防 React onClick 直传 MouseEvent
  const safeOverride = typeof override === 'string' ? override : undefined;
  const prompt = (safeOverride ?? composerValue).trim();
};
```

## 工程化保险

加 ESLint 规则 `react/jsx-no-bind` + 自定义规则识别"可选 string 参数 + onClick={handler}"
组合。但实际上**所有 React 项目都该养成"`onClick={() => handler()}`"肌肉记忆**——
event handler 绑定永远不要"裸传"业务函数。

## 教训

1. **React event handlers 永远传 event 作为首参**——这是 React 17+ 合成事件契约
2. **TypeScript 函数类型 contravariance** 在这里不保护你：宽参数函数可以赋给窄参数签名
3. **`??` 和 `||` 看的是真假值/null-ish**——MouseEvent 对象是真值，跳不到 fallback
4. **症状离根因隔了 N 层**：handler 写于 page.tsx:376，bug 在 Composer 子组件 page.tsx:1594 `onClick={onSend}`——子组件传 prop 链很容易掩盖

## 关联

- 修复 commit：本 PR
- React 文档：https://react.dev/learn/responding-to-events#passing-event-handlers-as-props
