尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

深入理解JSX:从语法到与React的绑定关系

深入理解JSX:从语法到与React的绑定关系 理解 JSX从语法到与 React 的关系我在社区里接触过不少从 Vue 转过来的朋友也包括一些刚接触前端的新人大家聊到 React 时最先感到困惑的往往不是组件化思想也不是状态管理反而是 JSX 这个“看起来像是把 HTML 写进了 JavaScript”的语法。它既不是模板字符串也不是真的 HTML但它在 React 中又是如此的基础几乎每一行组件代码都离不开它。今天我想把这几年实际使用 JSX 的积累和思考完整地梳理一遍重点聚焦“JSX 到底是什么”以及“它和 React 之间究竟是怎样的绑定关系”希望能给还在困惑中的同学一个比较通透的解释。1. 从“为什么需要 JSX”说起React.createElement 的痛点1.1 没有 JSX 之前我们怎么写 UI在系统学习 JSX 之前我还是想先聊聊它的前身——React.createElement。很多新手写 React 时完全绕过了这个 API直接从 JSX 开始写这导致遇到报错时尤其是那些提示“Reactis not defined”的错误时有些同学会一头雾水我明明没有直接使用 React 变量为什么它非要我导入 React这是因为 JSX 只是“甜”的写法它的真实身份是React.createElement的语法糖。我们来看一个最典型的例子下面这段 JSX 代码const element h1 classNamegreeting你好React/h1;它在编译之后会变成这样的 JavaScript 代码const element React.createElement(h1, { className: greeting }, 你好React);React.createElement接收三个参数实际参数可以更多第一个是元素类型可以是原生标签字符串比如h1、div也可以是组件函数或组件类第二个是属性对象用来描述这个元素上的所有属性第三个及之后的参数是子元素。每次写这样一个元素都要手动调用createElement当 UI 结构嵌套变深代码的可读性和维护性会直线下降。我印象很深的一次经历是刚用 React 写一个复杂表单页面时尝试不用 JSX全部用createElement来构建 DOM那次的代码数量几乎是 JSX 写法的两倍而且括号层级极深稍不注意就会漏掉一个右括号排查成本非常高。从那时起我就深刻体会到JSX 的出现绝不是锦上添花而是 React 开发体验的一次重要革新。1.2 JSX 不是模板语言它在“描述 UI 结构”这里有必要强调一个最常见却又容易被忽视的认知JSX 不是模板语言。传统的模板语言比如 Vue 的template或 Handlebars有自己的语法体系比如{{ }}插值、v-for循环、v-if条件判断等这些模板语法需要在运行时被框架解析然后转换成对应的渲染逻辑。JSX 则完全不同。它本质上是 JavaScript 表达式是直接在 JavaScript 代码中嵌入 XML 风格的结构描述。这意味着它天然具备了 JavaScript 的完整表达能力。我们可以在 JSX 中使用map来遍历数组生成列表可以用三元表达式做条件渲染可以随意调用函数使用const、let等变量因为这些操作本就是 JavaScript 的一部分。一个直观的对比下面是一个模板语言和 JSX 在循环渲染上的差异模板语言思路示意ul li v-foritem in list :keyitem.id{{ item.name }}/li /ulJSX 的写法const list [{ id: 1, name: 张三 }, { id: 2, name: 李四 }]; function List() { return ( ul {list.map((item) ( li key{item.id}{item.name}/li ))} /ul ); }注意到区别了吗JSX 里的循环直接就是 JavaScript 的map方法条件判断可以直接用或三元表达式。写 JSX 不需要学习一套新的循环或条件语法只需要会写 JavaScript 即可。这个设计特别适合那些想在 UI 表达上保持 JavaScript 心智模型的开发者。1.3 JSX 让结构和事件绑定“各得其所”还有一个被很多文章忽略的点就是 JSX 在结构、样式、事件绑定上的统一。传统开发中HTML、CSS、JavaScript 被隔离在不同的文件里我们必须在三者之间来回跳转。JSX 出现之后组件内部的 UI 结构、内联样式绑定、事件处理逻辑可以非常自然地放在一起这种“高内聚”的设计正是 React 组件化思想的重要体现。比如这个简单的计数器组件function Counter() { const [count, setCount] useState(0); return ( div p当前计数{count}/p button onClick{() setCount(count 1)}增加/button /div ); }结构、逻辑、事件处理在同一段代码里组件内部自我闭环可读性比拆分成三个文件再互相引用高出不少。当然这不是在否定关注点分离的价值而是说明在组件粒度上JSX 把“UI 相关的一切”聚合在一起是更符合人类认知习惯的。2. JSX 的执行流程编译器怎么把“类 HTML”变成 JavaScript2.1 从 babel 插件到 AST 的转变JSX 并不是原生浏览器能直接识别的语法因此需要编译工具做一次转换。当前主流方案是使用 Babel 的babel/plugin-transform-react-jsx插件或是在新版本 React 中使用react-jsx这个 transform 方式。这一编译过程本质上是一个标准的“解析—转换—生成”流程。Babel 会先读取你的源代码将 JSX 片段解析为抽象语法树AST然后在 AST 层面把 JSXElement 节点转换成jsx()函数调用节点最后再生成目标 JavaScript 代码。如果要亲手观察编译产物我推荐一个简单粗暴的方法去 Babel 官网的在线 Replbabeljs.io/repl里左侧写一段 JSX右侧选择 React 预设就能立刻看到转换后的代码。这也是我早期学习 JSX 时最常用的一种辅助手段看着左侧的div.../div变成右侧的React.createElement(div, ...)那种“神秘感”马上就消失了。2.2 运行时还是编译时JSX 的“去语法糖”过程提到这里不少同学会问JSX 是在编译时就被完全去掉了吗运行时还需要做什么从 React 17 开始官方推出了新的 JSX Transform编译后的产物不再要求每个文件都显式引入React。旧版 transform 通常会生成这样的代码import React from react; function App() { return React.createElement(div, null, Hello); }而新版 transform 则会变成import { jsx as _jsx } from react/jsx-runtime; function App() { return _jsx(div, { children: Hello }); }注意这里的区别新方式从react/jsx-runtime导入一个jsx函数不再依赖文件内引入React变量。所以在新项目里如果你只写 JSX 而不使用useState等 Hook 时可以不用导入React。不过在很多老项目或者尚未升级到 React 17 的项目中仍然需要手动import React from react否则编译后会报React is not defined。这个细节很多人容易忽略也是面试中经常被问到的问题JSX 是编译时处理的还是运行时处理的答案是语法转换发生在编译时而createElement或jsx函数的执行则发生在运行时。也就是说JSX 并没有在编译时变成真实的 DOM 节点它只是变成普通 JavaScript 函数调用真正创建虚拟 DOM 结构的工作是在运行时完成的。2.3 自定义组件大写开头是“铁律”JSX 中有一个硬性规则组件名必须以大写字母开头而原生标签使用小写。原因很简单编译器需要靠首字母大小写来区分“这是一个 HTML 标签”还是一个“自定义组件”。例如下面这段代码function MyComponent() { return divHello/div; } // 正确 const a MyComponent /; // 错误会被当成原生标签 const b mycomponent /;mycomponent /会被编译器解释为原生元素最终生成类似React.createElement(mycomponent, null)的代码也就是试图创建一个名为mycomponent的 HTML 标签这当然不是我们想要的。这一点是新手踩坑重灾区很多人复制代码时不小心把组件名写成了小写结果页面渲染出来的全是空标签甚至直接报错。所以只要你看到“组件没有渲染”这类现象第一优先检查的就是组件名是否大写开头。3. JSX 的设计思想为什么 React 选择它而不是模板语法3.1 UI 即函数JSX 与函数式编程的呼应React 官方文档里有一句经典的话“React 认为渲染逻辑本质上与其他 UI 逻辑内在耦合。”这句话直白地解释了他们选择 JSX 的原因——在 React 的设计哲学中UI 是数据的函数页面上的任何一部分界面都应该是“给定输入返回输出”的纯函数。JSX 正好天然契合这种思想。因为在 JSX 中我们可以在一个组件函数里随心所欲地使用 JavaScript 的表达式数据如何流转、如何转换、如何最终渲染这个流程完全透明且可控。对比而言模板语法往往需要设计一套新的数据绑定指令比如{{ }}、v-if、v-for它在自由度上天然受限因为模板语言的表达能力取决于框架实现了哪些指令。举一个比较极端的例子。在 JSX 中如果你想实现一个“根据用户权限显示不同按钮”的需求可以这样写function ActionButton({ user }) { const permissions user.permissions || []; const isAdmin permissions.includes(admin); return ( div {isAdmin ? ( button管理控制台/button ) : ( button普通操作/button )} /div ); }这里就是普通的 JavaScript 三元运算完全不需要学习模板中的v-if / v-else。任何 JavaScript 的循环、分支、表达式都可以直接塞进 JSX 的大括号中这种无缝衔接带来的表达能力是模板语法难以比拟的。这也是我在从 Vue 转向 React 之后感觉最“爽”的地方——我不需要记住“这个指令在模板里叫什么”只需要问自己“这在 JavaScript 里怎么写”。3.2 与 Vue 模板的对比思考不同生态的取舍既然提到了 Vue我不妨展开说说 JSX 和 Vue 模板的差异。Vue 有自己完善的模板语法比如v-if、v-for、v-bind、v-on等。Vue 的模板是在 HTML 的基础上做扩展它对设计师和后端程序员相对友好因为它看起更像传统 HTML。但它也有一个缺点模板内的表达式能力受限一些复杂的逻辑往往需要借助计算属性、方法或指令来实现。Vue 本身也支持 JSX 或渲染函数但在主流开发中大家更习惯使用模板。React 则完全拥抱“JavaScript 至上”的理念。社区里有句话“React 是一个拥有更广泛 JavaScript 开发者的 UI 库”这种定位恰恰跟 JSX 的设计一脉相承——只需要 JavaScript就足以构建任何复杂 UI。如果我们从团队协作的角度看两者其实没有绝对的好与坏更多是匹配团队已有能力和项目类型。如果团队里有比较多对 JavaScript 特别熟悉的工程师那么 JSX 的学习成本很低如果团队构成偏模板或后端出身Vue 模板风格的上手门槛可能更低一些。3.3 JSX 的“受控”观念UI 跟着数据走JSX 还有一个非常核心的设计观念——UI 的“受控”。在模板语法中开发者通常需要关注数据和 DOM 节点的双向关联而在 React 的世界里UI 是数据的产物。JSX 中的一切属性、文本、列表都是基于当前的 JavaScript 数据计算出来的。我举个例子感受一下function Welcome({ user }) { return ( section h1{user ? 欢迎回来${user.name} : 你好游客}/h1 p{user user.lastLogin ? 上次登录${user.lastLogin} : 首次访问}/p /section ); }这段代码中界面内容完全由user对象决定不需要手动操作 DOM也不用关心视图什么时候更新。只要user变了组件重新执行渲染函数JSX 就会计算出新的结构React 再把变更同步到真实 DOM。这种数据驱动视图的模式在 JSX 的加持下是极其自然、顺畅的。4. JSX 的常见使用细节与高级技巧4.1 大括号里能放什么、不能放什么JSX 中可以使用大括号{}来嵌入 JavaScript 表达式这是所有 JSX 学习者必须掌握的语法。但大括号里能放什么、不能放什么并不是所有人都门清。首先大括号里可以放任何 JavaScript 表达式包括变量、函数调用、三元运算、数组的map、对象属性访问等等。例如const name 张三; const age 25; function getGreeting(user) { return 你好我是 ${user.name}今年 ${user.age} 岁; } const element div{getGreeting({ name, age })}/div;但有一个非常常见的误区大括号不能直接放“语句”。比如if、for、switch这些是不能直接写在 JSX 大括号里的。你不能这样写const element ( div {if (condition) { return 是; } else { return 否; }} /div );这是语法错误。正确做法是把逻辑提取出来在 JSX 外部用普通 JavaScript 计算好再放入大括号中或者使用 IIFE立即执行函数表达式、三元表达式、运算符等表达式化写法。另一个细节是大括号里的注释也需要用 JavaScript 的注释方式比如return ( div {/* 这是注释不会在页面上显示 */} p内容/p /div );4.2 属性绑定的几种形态JSX 中的属性绑定和 HTML 属性有很大的相似性但也有几个关键区别。第一动态属性值需要放在大括号中const url https://example.com; const size 32; img src{url} width{size} height{size} alt示例图片 /第二JSX 中部分属性名和 HTML 不同因为class是 JavaScript 的保留字所以在 JSX 中要写成className同理for要写成htmlFor。另外tabindex在 JSX 中要写成tabIndexreadonly要写成readOnlymaxlength要写成maxLength。这些命名调整虽然是小事但在迁移或手写时非常容易出错。第三所有属性都会以字符串或表达式值的形式传给组件。对于原生 DOM 元素React 会做校验只有合法的 DOM 属性才会被实际渲染到 DOM 上对于自定义组件属性全部会收集到一个 props 对象里。第三点具体展开比如你给一个原生div传了一个自定义属性customAttr旧版本 React 会透传到 DOM 上但新版本通常只接受标准属性非标准属性会有告警。不过对于自定义组件props 可以包含任何字段这是组件通信的基础。4.3 条件渲染的最佳实践条件渲染是 JSX 使用频率最高的技巧之一。我总结了几种常用模式并列出它们各自的适用场景。模式一短路运算适用于“满足条件就渲染否则什么都不渲染”的场景function Notification({ hasMessage, children }) { return div{hasMessage p{children}/p}/div; }这里有个隐藏的坑很多新手会踩——当hasMessage不是布尔值而是数字0时页面会渲染出一个0。因为0 something的结果是0React 会把数字0当作文本渲染出来。所以如果你的条件可能是0、NaN等非布尔值最好先转成布尔值比如hasMessage 0 ...或Boolean(hasMessage) ...。模式二三元表达式适用于“满足条件渲染 A否则渲染 B”的场景function UserStatus({ isLoggedIn }) { return ( div {isLoggedIn ? LogoutButton / : LoginButton /} /div ); }三元表达式的好处是“两分支皆可见”无论是看代码还是调试意图都非常明确。模式三变量存储当条件分支较多或者渲染逻辑较长时建议在 JSX 之前把节点计算好再放进 JSX这样可读性最佳function Greeting({ role }) { let content; if (role admin) { content AdminDashboard /; } else if (role user) { content UserProfile /; } else { content GuestView /; } return div{content}/div; }我个人的习惯是分支超过两个就优先用变量存储或函数提取尽量避免在 JSX 内部堆叠多层三元表达式因为那种连环三元运算符的可读性实在太差了。4.4 列表渲染与 key 的本质理解列表渲染也是 JSX 必不可少的技能。通常配合Array.prototype.map使用const todos [ { id: 1, title: 学习 JSX }, { id: 2, title: 学习 React }, { id: 3, title: 编写示例 } ]; function TodoList() { return ( ul {todos.map((todo) ( li key{todo.id}{todo.title}/li ))} /ul ); }关于key我见过太多开发者只是机械地加上它却不明白它的作用。简单说key帮助 React 判断列表中哪些元素发生了变化、被添加或是被移除它是 React 进行 diff 时的重要依据。如果使用数组索引作为 key在列表项顺序会变化、插入或删除的场景下可能导致错误渲染或性能问题。如果列表项本身没有唯一 id并且列表不会发生排序和插入那用索引是可以接受的但一旦有增删排序就一定需要真正的唯一值。还要提醒一点key是 React 的特殊属性它不会传给组件。也就是说你在组件内部通过props.key是拿不到的只能在父级列表渲染中使用。如果需要把某个唯一值传给组件应该用另一个属性名比如id。这也是一个新的小坑。4.5 内联样式的“怪异”写法和 HTML 不同JSX 中内联样式不接收字符串而是接收一个对象。属性名采用驼峰命名法。例如function Box() { const style { backgroundColor: #000, color: #fff, fontWeight: bold, padding: 12px 20px, borderRadius: 4px }; return div style{style}内容/div; }这里有几个细节值得注意CSS 属性名中的连字符要改成驼峰比如background-color变成backgroundColorfont-weight变成fontWeight数值属性默认单位是px但也有例外比如lineHeight可以不写单位flex之类的属性也不适用px作为单位。如果你需要带单位的值实际写起来是这样的div style{{ fontSize: 16px, lineHeight: 1.5 }}文本/divfontSize用了字符串因为它是px单位lineHeight直接用数值表示倍率。这些细微差别实践多了自然就记住了。5. React 与 JSX 的深度绑定与边界探讨5.1 React 一定需要 JSX 吗直接回答不需要。React 完全可以脱离 JSX 使用。前文已经提到JSX 只是createElement的语法糖我们完全可以直接写createElement来构建组件。甚至 React 的源码中JSX 并不是 React 本身的必备部分它只是官方推荐的一种开发体验优化手段。如果真的禁用 JSX组件会写成下面这样function Welcome(props) { return React.createElement(div, null, Hello, ${props.name}); } function App() { return React.createElement( div, null, React.createElement(Welcome, { name: 张三 }), React.createElement(Welcome, { name: 李四 }) ); }用是能用的只不过麻烦了不少。所以在实践中几乎不可能见到不用 JSX 的 React 项目但理解这一点有助于我们揭开 JSX 的神秘面纱——它并不神奇只是一个让代码更优雅的编译期工具。5.2 JSX 只能在 React 中使用吗这也是一个容易混淆的问题。JSX 并不是 React 的专属语法它只是一种“嵌入 JavaScript 的 XML 风格语法”。通过不同的编译插件JSX 可以被转换为不同的目标代码服务于不同的框架或库。比如 Solid.js、Preact、Inferno 等框架都支持 JSX甚至 Vue 也可以通过插件支持 JSX 编写。Solid.js 就是一个很好的例子它也用 JSX但它的编译方式和 React 完全不同。Solid 会把 JSX 编译为真正的响应式绑定代码而不是虚拟 DOM 创建函数。这说明 JSX 是一种“语法容器”怎么解释、怎么转换取决于编译器配置。因此更严谨的说法是JSX 是一种与 React 深度绑定、但又不完全从属于 React 的技术方案。只不过在绝大多数人的认知里提到 JSX 就默认指 React 生态里的 JSX 罢了。5.3 JSX 对 React 开发体验的核心贡献如果让我用一句话总结 JSX 对 React 的贡献我会说JSX 让 UI 代码变得像 JavaScript 代码一样自然。它去掉了模板层的“心智税”让开发者可以用同一种语言完成所有工作——定义数据、计算逻辑、描述界面、绑定交互。正是这种“零损耗”的语言互通性React 才得以发展出庞大的组件生态也让 Hooks 这类基于函数闭包的机制能够无障碍地嵌入 JSX。你可以在 JSX 中调用自定义 Hook可以在大括号里使用所有 JavaScript 能力这种自由感是 React 开发者最珍视的体验之一。我还记得第一次用 JSX 写一个复杂的瀑布流组件在模板语法时代我需要学习模板如何实现循环、如何动态绑定 class但用 JSX 时这一切不过是map和三元表达式非常自然地就写出来了。那种“这本来就该这样写”的感觉正是 JSX 最佳的一面。6. JSX 的边界与进阶认知从“会用”到“理解”6.1 为什么不要把复杂逻辑全堆在 JSX 里尽管 JSX 允许在花括号中写任意表达式但我还是想强调一个实践经验复杂逻辑不要直接堆在 JSX 里。原因有两点一是可读性二是有利于测试。一段“精致”但令人窒息的三层嵌套箭头函数三元表达式虽然运行没问题但维护它的同事大概率会在心里抱怨。更推荐的做法是先把逻辑抽成具名函数或变量JSX 处只保留结果引用// 不推荐 return ( div {list .filter((item) item.status active) .sort((a, b) b.priority - a.priority) .map((item) ( Card key{item.id} title{item.title} description{item.description} / ))} /div );// 推荐 const activeSortedItems list .filter((item) item.status active) .sort((a, b) b.priority - a.priority); return ( div {activeSortedItems.map((item) ( Card key{item.id} title{item.title} description{item.description} / ))} /div );把逻辑提前计算出来还能顺手对activeSortedItems做单元测试这样的代码更符合工程化要求。6.2 JSX 不是 HTML几个经典误区很多刚接触 JSX 的人会误以为它和 HTML 是“一回事”其实不然。JSX 在属性以驼峰命名、闭合规则、注释方式、样式写法上与 HTML 存在系统性差异。下面这张表能帮大家快速对照项目HTMLJSXclass 属性classfooclassNamefoofor 属性fornamehtmlForname内联样式stylecolor: redstyle{{ color: red }}注释!-- 注释 --{/* 注释 */}属性命名tabindex、readonlytabIndex、readOnly标签闭合部分标签可不闭合所有标签必须闭合或自闭合这些差异并非刻意制造而是为了贴近 JavaScript 的语法习惯并保持解析的唯一性。例如JSX 中class无法作为属性名是因为它在 JavaScript 中属于保留字驼峰命名则是为了统一 JavaScript 的命名规范。理解了背后的原因就能更容易记住这些差异。6.3 Fragments如何合法地返回多个兄弟节点JSX 有个限制组件返回的 JSX 表达式必须有一个唯一的根节点。这是由createElement的单根逻辑决定的。早期的解决方案是包一层多余的div但这会污染 DOM 层级尤其在表格行、列表项等对结构敏感的场景中会带来麻烦。于是 React 提供了Fragment组件它不会渲染出真实 DOM 节点只作为逻辑上的容器function Row() { return ( td单元格 1/td td单元格 2/td / ); }短语法.../是Fragment.../Fragment的便捷写法两者含义相同。在需要循环渲染时Fragment 也非常好用比如表格中根据数据动态生成多个tr。不过要注意短语法无法传入key如果有 key 需求需要写成显式Fragment key{...}。这个细节我在实际项目中踩过坑。当时渲染一个表格行时从后台拿到一个字段数组每个字段要对应一个td最初我直接用div包裹导致表格结构被破坏样式也乱了。后来改用Fragment并加上key问题迎刃而解。可以说 Fragment 是 JSX 语法中对 DOM 层级保持“纯净”的重要工具。6.4 JSX 与 TypeScript 的结合类型安全下的 JSX在现代 React 项目中TypeScript 几乎已成为标配JSX 在 TS 中也有自己的语义。当我们写一个组件时interface ButtonProps { children: React.ReactNode; onClick?: () void; color?: primary | secondary; } function Button({ children, onClick, color primary }: ButtonProps) { return ( button className{btn btn-${color}} onClick{onClick} {children} /button ); }TypeScript 会对 JSX 的返回值、props 属性做静态类型检查能够有效避免拼写错误、类型不匹配等问题。特别是像children、事件处理器、可选属性在接口中定义清楚后编辑器会有非常棒的智能提示这对团队协作和重构特别有利。有一个小细节.tsx文件扩展名对泛型箭头函数有语法限制比如T,(arg: T): T这种写法后面的逗号是必要的否则 JSX 会误认为是一个标签的开始。涉及泛型组件时建议在.tsx中多加逗号或改用 function 声明这样能规避编译报错。6.5 JSX 的调试工具链它帮你看到了什么现代前端开发中浏览器 DevTools 和 React Developer Tools 已经是最基本调试工具。当我打开 React 开发者工具时组件树视图会直接展示 JSX 结构的映射关系——你可以看到每个组件的 props、state、hooks甚至可以在源码面板中点击跳转到对应的 JSX 文件。JSX 还有一个特别好的地方是因为它是 JavaScript所以在构建层面比如 webpack、vite可以利用已有的 JS 处理链路进行模块热替换、按需加载、tree-shaking。这些能力是模板语法需要额外适配的。在调试复杂业务时借助 React DevTools 的 profiler 可以清晰地看到组件何时重新渲染、由哪个状态变化导致。配合 JSX 的可读性定位性能问题的效率会高很多。7. 常见报错与排查思路从错误信息倒推 JSX 规则7.1 组件名小写导致无法渲染这是我在新手群里看到最多的报错之一。写过div和component之后有时切换会不小心把自定义组件写成小写开头此时 React 会将之视作一个未知 HTML 标签可能有如下表现页面上出现一个空白标签并没有内容输报错Warning: myComponent / is using incorrect casing. Use PascalCase for React components, or lowercase for HTML elements.解决办法其实很简单把组件名改成大写开头即可。建议在项目里开启 ESLint 的react/jsx-pascal-case规则从工具层面拦截这类低级错误。7.2 左右闭合不匹配JSX 中所有标签必须正确闭合。自闭合标签img /、input /等也必须显式写斜杠否则解析器会认为img开始了一个尚未结束的子节点从而引发“Expected corresponding JSX closing tag”之类的错误。遇到这报错我通常的做法是从最内层开始检查闭合标签是否匹配特别是当 JSX 层级较深、反复嵌套时。善用编辑器的括号匹配高亮也可以快速定位差异位置。7.3 “React is not defined”的两种情况这个报错在新老项目里都可能出现但要分情况看待。第一种情况项目还在用旧的 JSX transform而你误以为新版只需要import React省略。处理方式很简单在文件头部加上import React from react即可。第二种情况项目混用版本某个文件没有统一写法导致 ESLint 自动修复时删掉了React导入。这种通常排查一下文件头部的 import 语句就行。我的建议是新项目统一使用 React 17 的自动 runtime老项目统一显式导入React避免混用造成的混乱。7.4 属性名不合法或拼写错误因为 JSX 的 props 是交给 React 或自定义组件处理的属性名写错一般不会直接报编译错误而是导致功能异常。比如你写class而不是classNameReact 会产生告警提示你看一下是否是拼写问题。如果你在自定义组件里传了onclick而不是onClick事件监听器不会生效而且没有明确报错排查起来比较费劲。所以我一向强调借助 TypeScript 的 props 类型检查可以在编译前就拦截大多数属性名错误。同时建议打开编辑器的 TS/JSX 语法提示当属性名有疑义时会得到即时反馈。7.5 key 缺失或被误用列表渲染时React 会产生一条告警“Each child in a list should have a unique key prop.” 这不是编译错误但会影响渲染 diff 的正确性和性能尤其在列表动态变化时可能引发隐藏的渲染 bug。排查时建议先理清列表项的数据结构如果有唯一 id直接使用如果没有唯一值且列表不会重排可以使用索引。千万不要整层index一路用到底等遇到“列表项状态顺序错乱”时再返工就晚了。Key 应该放在列表渲染的最外层元素上而不是放在每个内部子元素上。8. 从实践角度聊聊我对 JSX 的长期观察与建议8.1 JSX 与 React 生态的协同演进React 生态中很多重要特性都围绕 JSX 展开。比如 Portals 允许把 JSX 渲染到任意 DOM 容器里Suspense 允许在组件树中声明异步加载的占位状态Error Boundaries 可以捕获子组件树中的渲染错误。这些能力都以 JSX 的声明式表达为基础保证开发者可以用一致的语法描述各种 UI 状态。随着 React 生态向 Concurrent Rendering 和 Server Components 演进JSX 的作用也在发生变化。比如 Server Components 中很多组件只在服务器端渲染它们可以用 JSX 描述界面但永远不会把 HTML 反馈给浏览器。这意味着 JSX 在未来 React 世界中依然是统一表达 UI 的工具这也正是它生命力持久的体现。8.2 对一些初学者学习路径的建议关于学习 JSX我不建议一上来就追求“所有高级技巧”或“所有边界情况”。初学时先把这四步走扎实彻底理解 JSX 在编译后的模样用 Babel 在线 repl 多观察几组转换结果。熟练掌握大括号插值变量、函数、三元、map是最高频的组合。掌握条件渲染与列表渲染并且理解key的工作原理。每次遇到报错认真阅读错误信息把报错和 JSX 的编译规则关联起来。一旦能把 JSX 当成本能的表达方式你会发现自己学习 React 组件的思路都清晰了很多。JSX 不只是“React 的模板”它更像是一面镜子映照出 React 对 UI 的哲学理解——UI 是由数据驱动的而 JSX 是描述这种数据结构最直接的途径之一。8.3 踩过不少坑之后的总结回顾这些年使用 JSX 的经历我最大的感受是JSX 的门槛并不高但理解它“为什么这样设计”的深度决定了一个 React 开发者能走多远。它把“界面描述”这件事彻底拉回了 JavaScript 的阵营让开发者不必切换思维模式。有人觉得 JSX 混乱因为它把结构样式和逻辑混在一起但我觉得在组件粒度上这种混合恰恰是合理的因为它以“组件”为单位组织代码而不是以“技术类型”为单位切分文件。如果你正在学习 React或者在使用 JSX 时经常遇到诡异报错强烈建议花一个下午把 JSX 的编译过程、核心规则和常见边界情况系统过一遍这绝对是投资回报率极高的一次学习。等你会读错误信息、会看编译产物、能理解 diff 时JSX 就不再是一个“神秘语法”而是你手里的得力工具。
返回列表