2、JSX语法深入:表达式、条件渲染、列表渲染与样式处理

好,咱们接着聊JSX。上一章我带你写了个简单的Hello World,你可能觉得「就这?不就是HTML套了个壳吗?」

嗯,我当时也是这么想的。直到我在项目中踩了几个坑,才发现JSX远没那么简单。这一章,我把JSX里最常用的几个硬核知识点掰开揉碎讲给你听。

2.1 JSX表达式:花括号里能写什么?

JSX里,只要看到{},就代表你要写JavaScript表达式了。说白了,花括号就是JSX和JS之间的桥梁。

核心规则:花括号里只能放表达式,不能放语句

什么是表达式?能算出值的东西就是表达式。比如:

const name = '张三';
const age = 25;

// ✅ 正确:这些都是表达式
const element = (
  <div>
    <p>姓名:{name}</p>
    <p>年龄:{age + 1}</p>
    <p>是否成年:{age >= 18 ? '是' : '否'}</p>
    <p>当前时间:{new Date().toLocaleString()}</p>
  </div>
);

// ❌ 错误:这是语句,不能放在花括号里
// const wrong = <div>{if (age > 18) { return '成年' }}</div>;

我个人习惯把复杂的表达式抽出来,先算好再放进去。比如:

const isAdult = age >= 18;
const statusText = isAdult ? '已成年' : '未成年';

const element = <p>状态:{statusText}</p>;

这样代码更干净,调试也方便。我在项目中见过有人把一长串三元运算符嵌套在花括号里,那阅读体验……嗯,你懂的。

2.2 条件渲染:显示什么,我说了算

实际开发中,经常要根据数据状态决定显示什么。React里做条件渲染,我常用的有四种方式。

2.2.1 三元运算符(最常用)

function Greeting({ isLoggedIn }) {
  return (
    <div>
      {isLoggedIn ? (
        <h2>欢迎回来!</h2>
      ) : (
        <button>请登录</button>
      )}
    </div>
  );
}

这个写法最直观,我90%的条件渲染都用它。

2.2.2 && 运算符(简洁但要注意)

function Notification({ message }) {
  return (
    <div>
      {message && <p>提示:{message}</p>}
    </div>
  );
}

这里有个坑,我曾经踩过:如果message是数字00 && <p>...</p>会直接渲染出0。因为0是假值,但React会把它渲染到页面上。所以用&&时,确保左边一定是布尔值。

避坑指南:我曾经在列表渲染时用 list.length && <List />,结果列表为空时页面上出现了一个大大的 0。正确的写法是 list.length > 0 && <List />

2.2.3 if/else 抽成函数

function getStatusText(status) {
  if (status === 'loading') return '加载中...';
  if (status === 'error') return '出错了';
  if (status === 'success') return '加载成功';
  return '未知状态';
}

function StatusPage({ status }) {
  return <p>{getStatusText(status)}</p>;
}

条件分支超过3个时,我建议用这种方式。JSX里保持干净,逻辑抽出去。

2.2.4 对象映射(优雅但小众)

const STATUS_MAP = {
  loading: '加载中...',
  error: '出错了',
  success: '加载成功',
};

function StatusPage({ status }) {
  return <p>{STATUS_MAP[status] || '未知状态'}</p>;
}

这个写法我个人很喜欢,尤其适合枚举类型的条件渲染。代码量少,扩展也方便。

2.3 列表渲染:把数据变成DOM

渲染列表,核心就一句话:用map()方法遍历数组,返回JSX元素。但有个关键点——key

const users = [
  { id: 1, name: '张三' },
  { id: 2, name: '李四' },
  { id: 3, name: '王五' },
];

function UserList() {
  return (
    <ul>
      {users.map(user => (
        <li key={user.id}>{user.name}</li>
      ))}
    </ul>
  );
}

关于key,记住三条:

  • key要稳定唯一,优先用数据的id
  • 不要用数组索引作为key(除非列表永远不会增删改排序)
  • key只在兄弟节点间比较,不同列表可以用相同key值

为什么不能用索引?我曾经在一个可拖拽排序的列表里用了索引作为key,结果拖拽后整个列表的DOM都乱了,复选框的状态全串了。那次排查花了我两个小时……后来老老实实改用id,问题瞬间解决。

2.4 JSX样式处理:内联样式与CSS类名

2.4.1 内联样式

JSX里的内联样式,写法跟HTML不一样。它接收一个对象,属性名用驼峰命名。

const styleObj = {
  color: 'blue',
  fontSize: '20px',   // 注意:font-size 变成 fontSize
  backgroundColor: '#f0f0f0',  // background-color 变成 backgroundColor
  padding: '10px 20px',
};

function StyledComponent() {
  return <div style={styleObj}>这是一段带样式的文字</div>;
}

// 也可以直接写在JSX里
function InlineStyle() {
  return (
    <div style={{ color: 'red', fontWeight: 'bold' }}>
      红色加粗文字
    </div>
  );
}

我个人不太推荐大量使用内联样式。一来性能不如class,二来没法用伪类(hover、focus等)。但动态样式(比如根据状态改变颜色)用内联很方便。

2.4.2 CSS类名

JSX里用className代替HTML的class。为什么?因为class是JavaScript的保留关键字。

// 单个类名
<div className="container">内容</div>

// 多个类名
<div className="container active highlighted">内容</div>

// 动态类名(常用)
function Button({ isPrimary }) {
  return (
    <button className={`btn ${isPrimary ? 'btn-primary' : 'btn-secondary'}`}>
      点击
    </button>
  );
}

小技巧:当类名逻辑复杂时,可以用第三方库 classnames 或自己写个工具函数。我项目中常用的是:cn('base-class', { 'active': isActive, 'disabled': isDisabled })

2.5 JSX注意事项:这些坑你别踩

最后,我总结几个JSX里容易出问题的地方。

注意事项 说明 示例
只能有一个根元素 JSX表达式必须被一个闭合标签包裹 <React.Fragment><></> 包裹
标签必须闭合 自闭合标签要写 /> <img src="..." /> 不能写成 <img src="...">
属性名用驼峰 HTML属性转JSX属性要改写法 onclickonClicktabindextabIndex
注释要写在花括号里 JSX里不能用HTML注释 {/* 这是注释 */}
布尔属性要小心 disabled 等属性传 truefalse <button disabled={isDisabled}>点击</button>

我曾经踩过的坑:在JSX里写 <input type="text" /> 时忘了加自闭合的斜杠,结果整个页面渲染不出来。还有一次把 className 写成了 class,控制台报错找了半天。这些细节,写多了自然就记住了。

好了,JSX的核心语法就这些。你想想看,其实并不复杂,就是一些规则需要记牢。下一章我们开始讲组件,那才是React真正精彩的地方。