HTML 转 JSX 指南:属性、style、事件与注释
为什么需要 HTML 转 JSX
在 React 开发中,我们经常需要把现成的 HTML 嵌进组件:设计稿导出、旧项目片段、UI 文档示例。直接粘贴进 JSX,控制台通常会先甩出:
Warning: Invalid DOM property `class`. Did you mean `className`?
JSX 长得像 HTML,本质却是 JavaScript 语法扩展,属性名与写法有一套独立规则。不想逐项手改时,可以用 HTML 转 JSX 工具:className、驼峰属性、style 对象、事件、注释与自闭合标签会一并处理。
HTML 与 JSX 的核心差异
1. class → className
class 是 JavaScript 保留字,JSX 里要用 className:
<!-- HTML -->
<div class="container card shadow">
<p class="text-sm text-gray-600">Hello</p>
</div>
{/* JSX */}
<div className="container card shadow">
<p className="text-sm text-gray-600">Hello</p>
</div>
2. style:字符串 → 对象
HTML 的 style 是字符串;JSX 要求对象,且 CSS 属性名用驼峰:
<!-- HTML -->
<div style="margin-top: 20px; background-color: #f0f0f0; font-size: 14px;">
content
</div>
{/* JSX */}
<div style={{ marginTop: '20px', backgroundColor: '#f0f0f0', fontSize: '14px' }}>
content
</div>
外层 {{ }}:外层是 JSX 表达式,内层才是对象字面量。连字符属性(margin-top)必须写成 marginTop,否则 React 可能静默忽略。
3. for → htmlFor
<label> 的 for 同样因保留字改名:
<!-- HTML -->
<label for="email">Email</label>
<input id="email" type="email">
{/* JSX */}
<label htmlFor="email">Email</label>
<input id="email" type="email" />
4. 自闭合标签
HTML 允许 <br>、<img>、<input> 不写闭合;JSX 要求标签闭合。没有子节点时用自闭合:
<!-- HTML -->
<img src="logo.png" alt="logo">
<br>
<input type="text" placeholder="Enter name">
<meta charset="utf-8">
{/* JSX */}
<img src="logo.png" alt="logo" />
<br />
<input type="text" placeholder="Enter name" />
<meta charSet="utf-8" />
charset 也要写成 charSet。若源码里已经是 <img />,转换器不应再叠成 <img / />。
5. 事件处理
HTML 事件全小写、值是字符串;JSX 用驼峰,值是函数引用或表达式:
<button onclick="handleClick()">点击我</button>
<button onClick={handleClick}>点击我</button>
| HTML 属性 | JSX 属性 |
|---|---|
onclick | onClick |
onchange | onChange |
onsubmit | onSubmit |
onkeydown | onKeyDown |
onmouseover | onMouseOver |
onfocus | onFocus |
onblur | onBlur |
转换器可以把 onclick="handleClick()" 收成 onClick={handleClick},复杂语句则包成 () => { ... }。贴进组件后仍要确认作用域里确有对应函数,并把依赖全局脚本的写法改成 props / hooks。
6. 其他属性名差异
| HTML 属性 | JSX 属性 | 说明 |
|---|---|---|
tabindex | tabIndex | 驼峰 |
readonly | readOnly | 驼峰;无值布尔写法也会改 |
maxlength | maxLength | 驼峰 |
cellspacing | cellSpacing | 驼峰 |
colspan / rowspan | colSpan / rowSpan | 驼峰 |
contenteditable | contentEditable | 驼峰 |
crossorigin | crossOrigin | 驼峰 |
autocomplete | autoComplete | 驼峰 |
autofocus | autoFocus | 驼峰 |
autoplay | autoPlay | 驼峰 |
acceptcharset | acceptCharset | 驼峰 |
defaultvalue | defaultValue | 非受控 input / textarea |
defaultchecked | defaultChecked | 非受控 checkbox / radio |
多词 HTML 属性在 JSX 里几乎都要驼峰化。
7. SVG 短横线属性
SVG 大量使用 stroke-width、fill-opacity、clip-path。React DOM 期望驼峰(strokeWidth 等);data-* / aria-* 则保持原样。迁移图标片段时,别只改外层 div,路径属性也要一起过一遍。
常见转换陷阱
注释写法不同
<!-- HTML 注释 -->
{/* JSX 注释 */}
条件注释、IE 专用标记在 React 里没有对应物,应删除或改写。
布尔属性
HTML 里属性存在即生效;JSX 可写布尔值,也可写无值属性:
<input disabled>
<input checked>
<input readonly>
<input disabled />
<input checked />
<input readOnly />
dangerouslySetInnerHTML
需要注入原始 HTML(富文本)时,不能直接设 innerHTML,要用:
<div dangerouslySetInnerHTML={{ __html: '<p>原始 HTML 内容</p>' }} />
API 名称刻意丑陋,提醒 XSS 风险:只对可信内容使用,并对不可信输入做消毒。
正则转换的边界
在线转换器多是属性层重写,不是完整 HTML 解析器。结构良好的片段效果好;含 Vue/Angular 模板语法、严重残缺、或属性值里裸露 > 的标记,应先清理/格式化再转。整站静态页更适合拆成小组件逐步迁移,而不是一次粘贴整页。
非受控默认值 vs 受控状态
设计稿常导出 <input value="…"> 或 <input checked>。在 React 里,只有 value / checked、没有 onChange 时,字段会变成只读(受控但值冻住了)。HTML 里那是「初始值」;React 里那是「永远等于这个值」。
| HTML 意图 | 更稳妥的 JSX 起点 | 说明 |
|---|---|---|
| 初始文本 | defaultValue="…" | 非受控;值先由 DOM 持有,再按需提升状态 |
| 初始勾选 | defaultChecked | checkbox / radio 同理 |
| 表单实时状态 | value={x} + onChange | 受控;你是唯一真相源 |
源码若已写成 defaultvalue / defaultchecked,转换器会改成驼峰。若导出仍用普通 value / checked 表示初值,粘贴后需自行改名,或在首次渲染前接上受控状态。嵌套难看时,可先走 HTML 格式化。
片段、多根节点与 key
HTML 片段经常是没有包裹层的兄弟节点。JSX 历史上要单一父节点;现在可用 <>…</> 或 React.Fragment。把转换结果放进 map 时,每个顶层项仍需要稳定的 key——转换器不会替你编造。更稳妥的做法是:把粘贴的卡片收进一个组件根,而不是把一堆平铺兄弟直接丢进父级循环。
推荐工作流
- 用 HTML 格式化 理清缩进与嵌套(可选)。
- 把片段贴进 HTML 转 JSX,检查 className、style、事件、注释与表单默认值。
- 复制到组件,接上真实 handler,删掉仍依赖全局脚本的调用。
- 需要进文档时,再用 HTML ↔ Markdown 处理说明文。
转换器应能覆盖:class / for、style 对象化、自闭合、属性驼峰(含 defaultValue / acceptCharset)、事件名与简单 handler、注释、常见 SVG 短横线属性。复杂业务逻辑仍要人工组件化。
实现注记
站内转换器是正则重写,不是浏览器 HTML5 解析器:按固定顺序处理属性、style、事件、注释与空元素闭合,并把注释槽位保护起来,避免注释里的 class= 被误改。它不会修复残缺嵌套、Vue/Angular 指令,或未加引号属性值里的 >。取舍是换本地、快速的草稿输出;请在 TypeScript 组件里打开结果核对,不要当成与 DOM 同构的保证。
小结
记住几条原则即可避开大部分坑:
- 属性名用驼峰——
className、htmlFor、onClick、charSet等 - style 是对象——连字符 CSS 属性同步驼峰
- 标签必须闭合——空元素用自闭合,且不要叠
/ /> - 事件是表达式——字符串脚本要改成函数引用
- 注释换写法——
<!-- -->→{/* */}
配合本地转换工具,HTML 片段迁进 React 会轻快很多;剩下的是组件边界与状态设计,而不是逐字符改属性名。
相关工具
相关文章
CSS 单位分界:px、rem、em 各管什么,换算怎么不出错
px 转 rem 的公式与速查表、rem 与 em 的分界(root 相对 vs 父级相对)、62.5% 技巧的利弊、媒体查询里的 rem 陷阱、浏览器最小字号限制,附在线 CSS 单位换算工具。
正则表达式匹配不上?排查这 6 个常见坑
正则表达式不匹配、regex匹配失败时如何排查?本文详解贪婪量词、标志位遗漏、特殊字符转义、环视断言、换行符处理、Unicode属性6大常见坑,附前后对比示例,帮你快速定位正则调试问题。
Hydration failed / Text content does not match 报错怎么解决?
Next.js/React SSR 最常见的水合报错完整指南:为什么服务端和客户端渲染不一致、时间戳/随机值/localStorage 三大来源、suppressHydrationWarning 的正确用法,配本站工具定位差异。