JSX 编译成什么
const el = <h1 className="title">Hi {name}</h1>;
经过 Babel / SWC 编译后大概是:
const el = React.createElement('h1', { className: 'title' }, 'Hi ', name);
返回的 el 是一个普通对象,叫 React Element:
{
type: 'h1',
props: { className: 'title', children: ['Hi ', 'World'] },
key: null, ref: null
}
JSX 不渲染,只是描述。React 拿到这棵元素树才决定怎么渲染。
Next.js / Vite 默认用新版 JSX transform(自动 import
jsx-runtime),所以你不写import React。但本质没变。
大写 vs 小写
<div> → type: 'div' (原生 HTML 元素,字符串)
<Header> → type: Header (组件,函数引用)
大小写决定行为。所以 function header() {...} 用 <header /> 会被当成原生 <header> 标签——必有 bug。
children 是什么
children 是一个 prop,可以是字符串、元素、数组、函数:
<Card>
<h1>标题</h1>
<p>内容</p>
</Card>
// props.children = [<h1>...</h1>, <p>...</p>]
可以传函数(render prop 模式):
<List>{(item) => <Row data={item} />}</List>
class 为什么叫 className
class 是 JS 关键字。同样 for → htmlFor。这是 JS 限制,不是 React 任性。
属性命名
JSX 属性是 camelCase:onClick、tabIndex、maxLength。原生 HTML 是 onclick、tabindex——别混。
唯一例外:aria-* 和 data-* 保持短横线(HTML 历史包袱)。
大括号里能写什么
<div>{anyJsExpression}</div>
表达式——求值后是字符串、数字、元素、数组、null、undefined、false。if/for 等语句不行:
// ❌
<div>{if (x) return ...}</div>
// ✅
<div>{x ? <A /> : <B />}</div>
<div>{x && <A />}</div> // x 为 0 时会渲染数字 0,注意
<div>{!!x && <A />}</div> // 安全做法
<div>{list.map(i => <Row key={i.id} />)}</div>
key 不是装饰
key 让 React 在列表 diff 时认出哪个是哪个。错误的 key 等于"重新创建"——state 丢失、动画失效。
list.map((item, i) => <Row key={i} />) // ❌ index 作 key,列表增删时全乱
list.map(item => <Row key={item.id} />) // ✅ 用稳定 id
Fragment
return (
<>
<Header />
<Main />
</>
);
<></> 是 React.Fragment 的语法糖——不渲染真实 DOM,只是把多个元素当一个返回。需要写 key 时不能用空写法,要 <Fragment key={...}>。
dangerouslySetInnerHTML
设置原始 HTML,绕过 XSS 防护:
<div dangerouslySetInnerHTML={{ __html: rawHtml }} />
名字带 dangerously 是故意吓人的——除非内容来源可信(如经过 DOMPurify 清洗的 markdown),别用。
坑
- JSX 表达式必须有单一根(或用 Fragment)
- boolean / null / undefined / 数组不渲染,但 数字 0 会渲染——
{count && <X />}当 count=0 时屏幕上会出现 "0" - HTML 注释
<!-- -->不行,要{/* */} - 内联 style 是对象:
style={{ color: 'red' }},值用 camelCase(backgroundColor)
→ 下一篇 组件分类与组合模式