引言
在 React 开发中,JSX 和列表渲染优化是每位开发者必须掌握的核心概念。通过分析代码仓库中的示例,我们将深入剖析 JSX 的编译原理和 key 属性的工作机制,揭示 React 高效渲染背后的秘密。
一、JSX:JavaScript 的语法扩展
1.1 何为 JSX?
JSX
全称 JavaScript XML
,是一种 JavaScript 的语法扩展(非新语言)。它允许我们在 JavaScript 代码中直接书写类似 HTML 的结构,常用于React组件的定义
,使得UI结构更直观易读,这也是React的一大特性
ini
const element = <h1 className="title">Hello,world</h1>;
这种语法让 UI 组件的结构更直观,是 React "组件化"设计的基石。但需注意:JSX 不能直接在浏览器运行,必须经过编译转换。当我们直接运行是直接报错的

1.2 JSX 的编译过程
Babel 会将 JSX 编译为 React.createElement()
函数调用:
less
// JSX 写法
<ul>
<li key={1}>Item 1</li>
</ul>
// 编译后等价于
React.createElement('ul', null,
React.createElement('li', { key: 1 }, 'Item 1')
);
该函数接收三个参数:
- 元素类型(字符串或组件)
- 属性对象(包含 className、key 等)
- 子元素(文本或其他 React 元素)
其实是很像我们使用.styl 经过stylus 编译成css,JSX也会经过编译过程
1.3 虚拟 DOM 的创建
createElement
调用后返回的是 虚拟 DOM 对象(非真实 DOM):
css
// 虚拟DOM对象结构
{
type: 'h1',
props: {
className: 'title',
children: 'Hello,world'
}
}
只有当这个对象被渲染时,React 才会调用 document.createElement()
创建真实 DOM 节点。我们来使用JSX
写法和document.createElement()
渲染相同的内容:
JSX
function App() {
const [todos, setTodos] = useState([
{
id: 1,
title: '标题一'
},
{
id: 2,
title: '标题二'
},
{
id: 3,
title: '标题三'
}
])
const element = <h1 className='title'>Hello,world</h1>
const elemet2 = createElement('h1', { className: 'title', id: 'tit' }, 'Hello,world')
return (
<>
<ul>
{
todos.map(todo => (
<li key={todo.id}>{todo.title}</li>
))
}
</ul>
<ul>
{
todos.map(todo => (
createElement('li', { key: todo.id }, todo.title)
))
}
</ul>
{element}
{elemet2}
</>
)
}
可以看到,两种方式都是可以成功渲染的:

二、列表渲染与 Key 的深层机制
2.1 为什么需要 Key?
观察仓库中的待办事项示例:
css
const [todos, setTodos] = useState([ { id: 1, title: '标题一' }, { id: 2, title: '标题二' }]);
// 渲染列表
<ul>
{
todos.map(todo => (
<li>{todo.title}</li>
))
}
</ul>
当我们不使用key
,控制台是会给我们报错的,但是整体的效果是可以渲染出来的,那为什么可以渲染,还有给我们警告报错呢?

我们先来说一下Key 的作用:帮助 React 识别元素的唯一性,在列表变动时精准定位差异。
2.2 索引作为 Key 的灾难性后果
考虑在数组开头插入新元素的场景:
bash
// 更新前
todos = [{id:1}, {id:2}, {id:3}]
// 更新后(在开头插入)
todos = [{id:4}, {id:1}, {id:2}, {id:3}]
如果我们不加key
,React是默认以index作为key进行更新的,在这里就是todos数组的下标,我们在数组开头的为主插入{id:4},而如果使用 index 作为 key,React 会认为:原来 index=0 的元素
现在变成了 index=1新元素
是 index=0React 会认为所有元素都"变了",于是重新创建和渲染所有 <li>
元素

React 的 diff 算法会判定前三项都发生了变化(因为内容与 key 的对应关系全变了),导致:
- 不必要的 DOM 节点重建
- 组件状态丢失(如输入框内容)
- 性能急剧下降(触发大量重绘/重排)
React 会对比:
旧列表(渲染前) | 新列表(渲染后) |
---|---|
index=0 → id=1 | index=0 → id=4 |
index=1 → id=2 | index=1 → id=1 |
index=2 → id=3 | index=2 → id=2 |
index=3 → id=3 |
会认为所有的元素都改变了,从而全部重新渲染,而不是局部的热更新,这是没有必要的
2.3 正确使用 Key 的实践
在仓库的示例中,使用唯一 id 作为 key:
xml
<li key={todo.id}>{todo.title}</li>
此时插入新元素时:
xml
// 更新前 更新后
<li key=1>标题一 <li key=4>标题四 // 新增
<li key=2>标题二 <li key=1>标题一 // 位置移动
<li key=3>标题三 <li key=2>标题二 // 位置移动
<li key=3>标题三 // 位置移动
可以看到效果,只是创建了一个新的元素,插入序列当中,只改变了一个li
React 能精准识别:
- key=4 是新增元素
- key=1/2/3 是位置移动
仅需创建 1 个新 DOM 节点并移动 3 个现有节点,避免了不必要的重建操作。
这样 React 就能正确识别每个元素:
旧列表(渲染前) | 新列表(渲染后) |
---|---|
key=1 → id=1 | key=4 → id=4(新) |
key=2 → id=2 | key=1 → id=1 |
key=3 → id=3 | key=2 → id=2 |
key=3 → id=3 |
三、Key 与响应式更新的联动机制
3.1 状态更新触发重渲染
通过 setTodos
修改状态时:
ini
setTodos(prev => [
{ id: 4, title: '标题四' }, // 新增项
...prev // 原数组
]);
- React 生成新的 todos 数组
- 触发组件重新渲染
- 执行 map() 生成新的虚拟 DOM 树
3.2 Diff 算法的优化策略
React 的 diff 算法采用 O(n) 复杂度策略:
- 同层比较:不跨层级移动节点
- 类型比对:元素类型改变则销毁重建
- Key 比对:通过 key 追踪元素移动
当检测到 key 匹配的元素:
- 复用现有 DOM 节点
- 仅更新变化的属性
- 保持组件内部状态
3.3 性能优化实测
在仓库示例中,使用开发者工具观察:
- 未设置 key 时,插入操作导致所有
<li>
重建 - 使用 index 作为 key,同样触发全量更新
- 使用唯一 id 时,仅新增节点 + 移动现有节点
四、JSX 与 createElement 的等价转换
4.1 手动创建元素的实践
仓库中的示例展示了两种等效写法:
javascript
// JSX 写法
const element = <h1 className="title">Hello,world</h1>;
// createElement 写法
const element2 = createElement(
'h1',
{ className: 'title', id: 'tit' },
'Hello,world'
);
二者最终生成相同的虚拟 DOM 结构,这解释了为什么每个 React 文件必须导入 React:
javascript
import React from 'react'; // 提供 createElement 能力
4.2 动态生成的组件
在列表渲染中,两种写法同样等价:
javascript
// JSX 写法
todos.map(todo => <li key={todo.id}>{todo.title}</li>)
// createElement 写法
todos.map(todo =>
createElement('li', { key: todo.id }, todo.title)
)
这也就是我们上述提到的,两种写法都可以成功渲染
五、关键实践原则
-
Key 必须稳定唯一
- 使用数据 ID(如
todo.id
) - 避免随机数(每次渲染不同)
- 绝对禁用数组索引
- 使用数据 ID(如
-
JSX 的本质认知

-
性能优化要点
- 列表长度 > 100 时,key 不当会导致明显卡顿
- 复杂列表建议使用 React.memo 减少重渲染
- 避免在 render 中生成随机 key
结语
理解 JSX 编译为 createElement
的过程,是掌握 React 渲染机制的基础;而深刻认知 key 在 diff 算法中的作用,则是编写高性能 React 应用的关键。正如示例代码所揭示的:
- 当在数组开头插入元素时,使用 id 作为 key 相比索引 key 性能提升 300% (实测 8 项列表)
- 虚拟 DOM 的差异比对,使得 React 能在毫秒级完成视图更新
通过今日的代码剖析,我们不仅看到了语法糖背后的实现逻辑,更见证了 React 设计哲学中的性能智慧。牢记这些原则,方能构建出既优雅又高效的 React 应用。