常见的 JSX 错误及其避免方法
JSX 看起来简单得有些欺骗性——它不就是 JavaScript 中的 HTML 吗?然而,即使是经验丰富的开发者也会在其特性上栽跟头,尤其是随着 React 的不断演进。随着 React 19 的自动 JSX 运行时、服务器组件以及现代框架不断变化的格局,这些错误产生了新的影响。以下是仍然困扰开发者的问题以及如何避免这些陷阱。
核心要点
- 使用数组索引作为 key 会导致协调问题并破坏 React 的并发特性
- 服务器组件需要与客户端组件不同的模式,尤其是在浏览器 API 方面
- 自动 JSX 运行时改变了代码的转换方式,需要正确的配置
- 内联函数和条件渲染模式可能会悄无声息地降低性能
JSX 的演进:为什么旧习惯会破坏新代码
React 17 引入的自动 JSX 运行时消除了在每个文件中导入 React 的需要,但也带来了新的困惑。你的 JSX 现在的转换方式不同了——jsx 函数取代了 React.createElement,而配置错误的构建工具可能会悄无声息地破坏你的应用。
在服务器组件中,风险更高。在客户端完美运行的 JSX,当它尝试访问 window 或在错误的上下文中使用 hooks 时就会崩溃。规则不仅仅是改变了,而是成倍增加了。
现代 React 中的关键 JSX 陷阱
1. 破坏性能的不稳定 Key
// ❌ 使用索引作为 key - 导致协调问题
items.map((item, index) => <Item key={index} {...item} />)
// ✅ 稳定的、唯一的标识符
items.map(item => <Item key={item.id} {...item} />)
使用数组索引作为 key 仍然是最具破坏性的 JSX 错误之一。在 React 的并发特性中,不稳定的 key 不仅会导致闪烁——它们还会破坏 Suspense 边界并在整个组件树中触发不必要的重新渲染。
2. 直接渲染对象
// ❌ 对象不是有效的 React 子元素
const user = { name: 'Alice', age: 30 };
return <div>{user}</div>;
// ✅ 渲染特定属性
return <div>{user.name}</div>;
这个错误信息自 React 15 以来就没有改变过,但开发者仍然尝试直接渲染对象。通过 TypeScript 的 JSX 类型推断,如果你的 tsconfig.json 配置正确,你可以在编译时捕获这个错误。
3. 创建新引用的内联函数
// ❌ 每次渲染都创建新函数
<Button onClick={() => handleClick(id)} />
// ✅ 使用 useCallback 创建稳定引用
const handleButtonClick = useCallback(() => handleClick(id), [id]);
<Button onClick={handleButtonClick} />
在 React 的渲染管道中,内联函数不仅会导致性能问题——它们还会破坏 memo 优化,并可能在整个组件树中触发级联更新。
Discover how at OpenReplay.com.
服务器组件:JSX 规则改变的地方
4. 在服务器组件中使用仅限客户端的代码
// ❌ 在服务器组件中崩溃
export default function ServerComponent() {
const width = window.innerWidth; // ReferenceError
return <div style={{ width }} />;
}
// ✅ 使用 client 指令或从客户端传递
'use client';
import { useState, useEffect } from 'react';
export default function ClientComponent() {
const [width, setWidth] = useState(0);
useEffect(() => {
setWidth(window.innerWidth);
}, []);
return <div style={{ width }} />;
}
服务器组件在浏览器之外执行,那里不存在 DOM API。这不是配置问题——而是架构问题。
5. 没有 Suspense 的异步组件
// ❌ 服务器组件中未处理的 promise
async function UserProfile({ id }) {
const user = await fetchUser(id);
return <div>{user.name}</div>;
}
// ✅ 使用 Suspense 边界包裹
<Suspense fallback={<Loading />}>
<UserProfile id={userId} />
</Suspense>
React 服务器组件可以是异步的,但如果没有适当的 Suspense 边界,它们要么会阻塞渲染,要么会因神秘的错误而崩溃。
现代 JSX 配置陷阱
6. JSX 运行时配置不匹配
// ❌ tsconfig.json 中的旧转换方式
{
"compilerOptions": {
"jsx": "react" // 需要导入 React
}
}
// ✅ React 17+ 的自动运行时
{
"compilerOptions": {
"jsx": "react-jsx" // 不需要导入 React
}
}
自动 JSX 运行时不仅仅是一种便利——它是优化包大小和服务器组件兼容性所必需的。这里的配置错误会导致只在生产环境中才会出现的静默失败。
7. 条件渲染反模式
// ❌ 返回 0 而不是什么都不显示
{count && <Counter value={count} />}
// ✅ 显式布尔转换
{Boolean(count) && <Counter value={count} />}
当 count 为 0 时,JSX 会渲染数字 0,而不是什么都不显示。这个错误在 React Native 中特别明显,因为文本节点需要适当的容器。
预防策略
正确配置你的工具:使用 eslint-plugin-react 设置 ESLint 并启用以下规则:
react/jsx-keyreact/jsx-no-bindreact/display-name
使用 TypeScript:通过正确的 JSX 配置,TypeScript 可以在编译时捕获大多数这些错误。启用 strict 模式并在你的 tsconfig.json 中正确配置 jsx。
了解你的运行时:知道你的组件是在服务器还是客户端运行。Next.js 14+ 通过 'use client' 指令使这一点变得明确,但这种思维模型适用于所有地方。
结论
2024 年的 JSX 错误不仅仅关乎语法——它们关乎理解你的代码在何处以及如何执行。自动 JSX 运行时改变了转换模型。服务器组件改变了执行模型。React 的并发特性改变了性能模型。
掌握这些基础知识,你就能编写不仅正确而且针对现代 React 能力进行优化的 JSX。最好的 JSX 是不可见的——它不碍事,让你的组件大放异彩。
常见问题
当你使用数组索引作为 key 时,React 无法正确跟踪哪些项目已更改、移动或被删除。这迫使 React 重新渲染比必要更多的组件,并可能导致在重新排序后状态与错误的组件关联。
虽然内联函数在功能上可以工作,但它们在每次渲染时都会创建新的引用,破坏 React.memo 优化,并可能导致子组件不必要地重新渲染。为了更好的可维护性,对依赖于 props 或 state 的事件处理程序使用 useCallback。
react 设置使用经典的 React.createElement 转换,需要在每个文件中导入 React。react-jsx 设置使用 React 17 引入的自动运行时,它在没有显式 React 导入的情况下处理转换,并生成更小的包。
Gain Debugging Superpowers
Unleash the power of session replay to reproduce bugs, track slowdowns and uncover frustrations in your app. Get complete visibility into your frontend with OpenReplay — the most advanced open-source session replay tool for developers. Check our GitHub repo and join the thousands of developers in our community.