2、JSX语法深度解析:JSX的本质与规则、嵌入JavaScript表达式、条件渲染与列表渲染、样式处理(内联与CSS模块)
好,咱们今天来聊聊JSX。说实话,很多React新手把JSX当成模板语言来用,这其实是个误解。JSX的本质,是JavaScript的语法扩展。说白了,它就是React.createElement()的语法糖。
我记得刚转React那会儿,看到JSX里混着HTML和JS,第一反应是「这玩意儿能跑?」。后来理解了它的编译原理,才恍然大悟——原来我们写的每个JSX标签,最终都会被Babel编译成一个个嵌套的createElement调用。
2.1 JSX的本质与规则
JSX不是模板,它是表达式。这意味着你可以把它赋值给变量、作为函数参数、甚至从函数里返回它。我习惯把JSX看作「用XML语法描述UI结构的JavaScript对象」。
有几个硬性规则,你最好刻在脑子里:
- 必须有一个根元素——React 16之前只能有一个,现在可以用
<></>(Fragment)包裹多个兄弟节点 - 标签必须闭合——自闭合标签要写成
<img />,别漏了那个斜杠 - 属性名用驼峰——
class变成className,tabindex变成tabIndex - 保留字不能直接用——比如
for要写成htmlFor
核心理解:JSX最终会被编译成这样的结构:
// 你写的 JSX
const element = <h1 className="title">Hello</h1>;
// 编译后
const element = React.createElement(
'h1',
{ className: 'title' },
'Hello'
);
嗯,这里要注意:React.createElement返回的是一个普通的JavaScript对象,叫做「React元素」。它不是真实的DOM节点,只是一个描述。React会拿这个描述去对比虚拟DOM,然后决定怎么更新真实DOM。
2.2 嵌入JavaScript表达式
在JSX里用JavaScript表达式,靠的是花括号{}。这个语法很直观,但有个坑——花括号里只能放表达式,不能放语句。
什么是表达式?能计算出值的都是表达式。比如变量、函数调用、三元运算符、数组map方法。什么是语句?if、for、switch这些控制流语句,不能直接放在花括号里。
const name = '张三';
const age = 28;
// ✅ 正确:变量、函数调用、三元运算符
const element = (
<div>
<p>姓名:{name}</p>
<p>年龄:{age + 1}</p>
<p>状态:{age >= 18 ? '成年' : '未成年'}</p>
</div>
);
// ❌ 错误:不能放 if 语句
// const wrong = <p>{if (age > 18) { '成年' }}</p>;
我的小技巧:如果你需要在JSX里做复杂逻辑判断,提前在外面算好,把结果塞进变量里。这样JSX部分保持干净,逻辑也容易调试。我在项目中经常用这种「计算与渲染分离」的方式。
还有一个细节:false、null、undefined、true这些值,在JSX里不会被渲染出来。但0和空字符串''会。我曾经踩过这个坑——数组长度为0时,{list.length && <List />}会在页面上渲染出一个「0」。正确的写法应该是{list.length > 0 && <List />}。
2.3 条件渲染与列表渲染
条件渲染,说白了就是「根据条件决定显示什么」。React里没有v-if、v-show这些指令,全靠JavaScript自己搞定。
我个人最常用的三种方式:
| 方式 | 适用场景 | 示例 |
|---|---|---|
| 三元运算符 | 二选一,简单明了 | {isLogin ? <UserPanel /> : <LoginBtn />} |
| 逻辑与 && | 要么显示,要么不显示 | {hasPermission && <AdminPanel />} |
| IIFE(立即执行函数) | 多分支复杂逻辑 | {(() => { if (...) return ... })()} |
避坑指南:我曾经在项目里用&&做条件渲染,结果数据是0的时候,页面上直接显示了一个「0」。原因我刚才说了——0在JSX里会被渲染。从那以后,我养成了习惯:所有&&前面都加一个布尔判断,比如{count > 0 && ...}。
列表渲染就更直接了——用Array.map()。你想想看,JSX里不能写for循环,那怎么遍历数组?map方法就是标准答案。
const users = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
];
const userList = (
<ul>
{users.map(user => (
<li key={user.id}>{user.name}</li>
))}
</ul>
);
这里有个关键点——key属性。为什么要有key?因为React需要靠key来识别哪些元素变了、哪些没变。如果不加key,React会采用「就地复用」策略,这在列表顺序变化时会导致各种诡异的问题。
我建议:key尽量用数据本身的唯一ID,不要用数组索引。为什么?因为索引在列表增删时会变化,导致React误判。当然,如果列表是静态的、永远不会变,用索引也没问题——但这种情况太少了。
2.4 样式处理:内联样式与CSS模块
React里写样式,主要有两条路:内联样式和CSS模块。咱们一个一个说。
内联样式
内联样式在JSX里写成对象形式,属性名用驼峰命名。比如font-size变成fontSize,background-color变成backgroundColor。
const style = {
color: '#333',
fontSize: '16px',
backgroundColor: '#f5f5f5',
padding: '10px 20px',
borderRadius: '4px',
};
const element = <div style={style}>带样式的文本</div>;
内联样式的优点:动态计算方便,作用域隔离。缺点也很明显:不支持伪类(:hover)、不支持媒体查询、代码冗余。所以我一般只在动态样式场景下用内联,比如根据状态改变颜色。
CSS模块
CSS模块是我在项目中用得最多的方案。它本质上就是CSS文件,但编译时会自动给类名加上哈希后缀,实现样式隔离。
用法很简单:
/* Button.module.css */
.button {
background-color: #007bff;
color: white;
padding: 8px 16px;
border: none;
border-radius: 4px;
}
.button:hover {
background-color: #0056b3;
}
.disabled {
opacity: 0.5;
cursor: not-allowed;
}
// Button.jsx
import styles from './Button.module.css';
function Button({ disabled, children }) {
return (
<button
className={`${styles.button} ${disabled ? styles.disabled : ''}`}
disabled={disabled}
>
{children}
</button>
);
}
为什么推荐CSS模块?
- 真正的样式隔离——类名自动加哈希,不会污染全局
- 支持所有CSS特性——伪类、媒体查询、动画,一个不落
- 开发体验好——写CSS就像写普通CSS,没有学习成本
- 配合TypeScript还能有类型提示
我记得有一次接手一个老项目,全局样式乱成一锅粥,改一个按钮的颜色影响了整个页面。后来我把所有组件都改成了CSS模块,世界清净了。从那以后,新项目我必上CSS模块。
当然,CSS模块也不是万能的。如果你需要动态主题、运行时切换样式,那还是得靠CSS变量或者内联样式。工具选型这事儿,没有银弹,看场景说话。
我的建议:组件库级别的样式用CSS模块,业务页面里偶尔的动态样式用内联。如果项目规模大、团队人多,可以考虑CSS-in-JS方案(比如styled-components),但那是另一个话题了。
好了,JSX的核心内容就这些。你想想看,其实JSX并不复杂,它只是把HTML和JavaScript揉在了一起,用JavaScript的语法来描述UI。理解了它的本质——createElement的语法糖——你就不会被那些花里胡哨的写法迷惑了。
下一章咱们聊聊React组件的生命周期和Hooks,那才是真正有意思的东西。