--- tags: [React, JSX, TSX, Frontend] create time: 2026-04-29 22:01 --- # JSX 语法 ## 概述 JSX(JavaScript XML)是 React 的核心语法糖。它让开发者能够在 JavaScript 中直接编写类似 HTML 的结构,经编译后转化为 `React.createElement()` 调用。理解 JSX 的底层机制是掌握 React 渲染原理的基础。 ## JSX 的本质 JSX 并不是模板字符串,编译后会变成普通的 JavaScript 对象: ```jsx // 你写的 JSX const element =

Hello, World!

; // 编译后的产物 const element = React.createElement( "h1", { className: "title" }, "Hello, World!" ); ``` > [!warning] 关键点 > - `React.createElement` 返回的是一个 **Plain Object**(虚拟 DOM 节点),不是真实的 DOM 元素 > - 这也是为什么 JSX 不会直接操作浏览器 API,而是由 React 的 Reconciler 统一管理 ## 表达式嵌入 在 JSX 中用 `{}` 包裹 JavaScript 表达式: ```jsx function FormattedDate({ date }) { return ( <>

Hello!

今天 {date.toLocaleDateString()}.

{/* 条件:JSX 不能用 if-else,用三元运算符或逻辑与 */} {date.getTime() < Date.now() &&

日期已过期

} {/* 数组渲染:每个元素需要 key prop */} {[1, 2, 3].map(n => ( {n} ))} ); } ``` ### 允许与不允许的表达式 | ✅ 允许 | ❌ 不允许 | |---------|----------| | `{variable}` | `{if (x) ...}` — JSX 中不支持语句 | | `{arr.map(...)}` | `{for (let i...)}` — 同上 | | `{cond && }` | `{func()}` — 函数无返回值时渲染 `undefined`(不报错但不生效)| | `{null}`, `{false}` | `{""}`, `{0}` — 会渲染空字符串或数字 `0`,常见陷阱 | > [!question] 为什么 `{cond ? : func()}` 能跑? > - 这是合法的 JS 表达式——只要 `func()` 返回一个值即可 > - 但建议写成 `{cond ? : }`,保持视觉一致性,避免混用 JSX 和函数调用 > [!note] null / undefined / boolean vs "" / 0 > - `null`、`undefined`、`true`、`false` 渲染结果为空——这是有意设计 > - `0` 和 `""` 会输出到 DOM,常见陷阱 ## 条件渲染模式 ```jsx // 模式1:&& 短路(推荐用于简单场景) {isLoggedIn && } // 模式2:三元运算符(适合两种视图切换) {isLoading ? : } // 模式3:提取为组件(复杂条件逻辑) return ; // 模式4:IIFE(极少使用,仅临时调试) {(function () { if (!data) return ; return ; })()} ``` ## 列表渲染与 Key ```tsx interface TodoItem { id: number; text: string; completed: boolean; } function TodoList({ items }: { items: TodoItem[] }) { return (
    {items.map((item) => (
  • {item.text}
  • ))}
); } ``` > [!question] 思考:为什么 React 要求列表项必须有 key? > - Key 帮助 React 识别哪些条目变了/增了/删了,从而最小化重渲染次数 > - 不建议用 index 作为 key(当列表顺序变化时会导致状态错乱) ## JSX 事件绑定 > [!tip] 与 HTML 事件的核心区别 > - **HTML**: `onclick="handler()"` → 字符串属性,浏览器运行时解析,在全局作用域求值 > - **JSX**: `onClick={handler}` → 驼峰命名,接收一个 **JS 函数引用**作为 props,不是字符串 > - JSX 没有"字符串属性"的概念——所有 `{} ` 内都是 JavaScript 表达式 ```jsx function ClickCounter() { const [count, setCount] = useState(0); // ✅ 正确:驼峰命名 onClick,值为函数引用(不加括号) const handleClick = () => setCount(c => c + 1); // ❌ 错误示范 //
); } ``` ### 事件传参 当需要向事件处理函数传递参数时,有两种写法: ```tsx interface TodoItemProps { id: number; onDelete: (id: number) => void; } function TodoItem({ id, onDelete }: TodoItemProps) { return ( ); } ``` > [!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 被删 ✗ > // 页面还没展示,三个都删光了 > > )); > > // ✅ onClick={() => onDelete(todo.id)} — 没有括号,只是"存一张任务卡" > todos.map(todo => ( > // 每次循环创建新的箭头函数,每个闭包封存了当前的 todo.id > // #1 的闭包记忆: todo.id = 1 > // #2 的闭包记忆: todo.id = 2 > // 用户点击哪个按钮,就取出对应的任务卡执行 > > )); > > // ✅ 如果不需要参数,直接传引用即可 > > // 等价于 → > // 但前者更简洁(没有额外创建箭头函数的开销) > ``` > > **什么是闭包?** 函数 + 它被创建时所在的词法环境。每次循环创建的箭头函数都是一个独立的闭包,记住了自己出生时的 `id` 值。即使后面 `id` 变了(比如在 for 循环中),闭包里的值也不会跟着变。 ### 为什么需要包装?核心逻辑 | 情况 | 做法 | 原因 | |------|------|------| | 不需要传参 | `onClick={handler}` | 直接传函数引用,React 在点击时调用 | | 需要传外部变量 | `onClick={() => handler(val)}` | 箭头函数作为中间层,传入所需参数 | | 需要访问原事件对象 | `onClick={(e) => { e.stopPropagation(); handler(id); }}` | 同时处理事件和自定义参数 | > [!warning] 常见陷阱 > - ~~`onClick="handler()"`~~ ❌ — 字符串赋值给 onClick prop,React 试图调用字符串时会报错 > - ~~`onClick={handler()}`~~ ❌ — 渲染时立即执行 handler,onClick = undefined(通常是函数返回值) > - ~~`onClick={handler}`~~ ❌ — 缺少参数,不知道要操作哪个元素 > - ✅ **正确姿势**:无参直接传引用 `onClick={handleClick}`;有参用箭头函数延迟执行 `onClick={() => handler(id)}` ### 常用合成事件类型 ```ts type MouseEvents = React.MouseEvent; type FormEvents = React.FormEvent; type ChangeEvents = React.ChangeEvent; type KeyboardEvents = React.KeyboardEvent; ``` > [!tip] TypeScript 事件类型的推导技巧 > - 不传泛型时:`event.target` 类型为 `EventTarget`(信息最少) > - 传入具体元素后:`event.currentTarget` 可以精确定位到对应 DOM 类型 > - 💡 **最佳实践**:优先使用 `currentTarget` 而非 `target`,避免类型收窄问题 ## JSX 基本规则 写 JSX 时有几条硬性规则需要遵守: ```tsx // 规则1:自闭合标签 —— 无子元素的元素必须加 / 头像 // 规则2:className 替代 class
内容
//
❌ — "class"是JS保留字 // 规则3:style 接收对象而非字符串
样式
// 驼峰命名:fontSize, backgroundColor, gridColumn 等 // 规则4:单根节点或 Fragment const Card = ({ title, children }) => ( <> {/* ✅ Fragment:不产生额外 DOM 节点 */}

{title}

{children}
); ``` > [!note] key 属性的唯一要求 > - `key` 只在数组上下文中有效——它是 React 的元数据,不会被传递给组件 > - 在自定义组件上使用 key 会报错:**key 只能在列表子元素上使用** ## Props 传递方式 ```tsx interface CardProps { title: string; className?: string; children: React.ReactNode; } function Card({ title, className = "", children }: CardProps) { return (

{title}

{children}
); } // 展开 props const attrs = { className: "card", title: "详情" }; ; // 动态属性名
// 受控 props(父组件控制子组件内部 state)