vault backup: 2026-04-30 10:02:04

This commit is contained in:
2026-04-30 10:02:04 +08:00
parent 750920d135
commit a275bfd668
+61 -6
View File
@@ -115,13 +115,23 @@ function TodoList({ items }: { items: TodoItem[] }) {
## JSX 事件绑定
> [!tip] 与 HTML 事件的核心区别
> - **HTML**: `onclick="handler()"` → 字符串属性,浏览器运行时解析,在全局作用域求值
> - **JSX**: `onClick={handler}` → 驼峰命名,接收一个 **JS 函数引用**作为 props,不是字符串
> - JSX 没有"字符串属性"的概念——所有 `{} ` 内都是 JavaScript 表达式
```jsx
function ClickCounter() {
const [count, setCount] = useState(0);
// 驼峰命名,不要写 onClick="handler()"
// ✅ 正确:驼峰命名 onClick,值为函数引用(不加括号)
const handleClick = () => setCount(c => c + 1);
// ❌ 错误示范
// <button onClick="handleClick"> → 字符串,React 无法调用
// <button onClick={handleClick()}> → 渲染时立即执行,onClick = undefined
// <button onclick={handleClick}> → 小写 onclick 不会被 React 识别为事件
// 阻止默认行为
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
@@ -151,18 +161,63 @@ interface TodoItemProps {
function TodoItem({ id, onDelete }: TodoItemProps) {
return (
<button onClick={() => onDelete(id)}>
{/* ⚡ 箭头函数包裹:创建闭包捕获当前 id */}
{/* ⚡ 箭头函数包裹:创建闭包捕获当前 id,延迟到点击时执行 */}
删除
</button>
);
}
```
> [!question] "闭包捕获当前 id"到底什么意思?
>
> **类比**:不用箭头函数就像点餐时一次性把所有订单全交给后厨(渲染时立刻执行);用箭头函数是给每个商品贴一张任务卡——卡片上记着当时的 `id`,等用户点击时才展开执行。
>
> **代码演示**(以列表渲染为例):
>
> ```tsx
> const todos = [{ id: 1, text: "买菜" }, { id: 2, text: "取快递" }];
>
> // ❌ onClick={onDelete(todo.id)} — 括号让它变成"调用语句"
> todos.map(todo => (
> // 第一次循环 → 立刻执行 onDelete(1),item1 被删 ✗
> // 第二次循环 → 立刻执行 onDelete(2),item2 被删 ✗
> // 页面还没展示,三个都删光了
> <button key={todo.id} onClick={onDelete(todo.id)}>删除</button>
> ));
>
> // ✅ onClick={() => onDelete(todo.id)} — 没有括号,只是"存一张任务卡"
> todos.map(todo => (
> // 每次循环创建新的箭头函数,每个闭包封存了当前的 todo.id
> // #1 的闭包记忆: todo.id = 1
> // #2 的闭包记忆: todo.id = 2
> // 用户点击哪个按钮,就取出对应的任务卡执行
> <button key={todo.id} onClick={() => onDelete(todo.id)}>
> 删除 {todo.text}
> </button>
> ));
>
> // ✅ 如果不需要参数,直接传引用即可
> <button onClick={handleClick}>点我</button>
> // 等价于 → <button onClick={() => handleClick()}>点我</button>
> // 但前者更简洁(没有额外创建箭头函数的开销)
> ```
>
> **什么是闭包?** 函数 + 它被创建时所在的词法环境。每次循环创建的箭头函数都是一个独立的闭包,记住了自己出生时的 `id` 值。即使后面 `id` 变了(比如在 for 循环中),闭包里的值也不会跟着变。
### 为什么需要包装?核心逻辑
| 情况 | 做法 | 原因 |
|------|------|------|
| 不需要传参 | `onClick={handler}` | 直接传函数引用,React 在点击时调用 |
| 需要传外部变量 | `onClick={() => handler(val)}` | 箭头函数作为中间层,传入所需参数 |
| 需要访问原事件对象 | `onClick={(e) => { e.stopPropagation(); handler(id); }}` | 同时处理事件和自定义参数 |
```
> [!warning] 常见陷阱
> - ~~`onClick={onDelete(id)}`~~ ❌ — 会在渲染时立即执行 `onDelete`,而非点击时执行
> - ~~`onClick={onDelete}`~~ — 缺少参数,不知道要删除哪个
> - ✅ **正确姿势**:`() => onDelete(id)` — 用箭头函数延迟执行并捕获参数
> - 💡 如果不需要参数,直接传引用即可:`onClick={handleClick}`(不要加括号)
> - ~~`onClick="handler()"`~~ ❌ — 字符串赋值给 onClick prop,React 试图调用字符串时会报错
> - ~~`onClick={handler()}`~~ ❌ — 渲染时立即执行 handler,onClick = undefined(通常是函数返回值)
> - ~~`onClick={handler}`~~ ❌ — 缺少参数,不知道要操作哪个元素
> - ✅ **正确姿势**:无参直接传引用 `onClick={handleClick}`;有参用箭头函数延迟执行 `onClick={() => handler(id)}`
### 常用合成事件类型