讲解

JSX 的规则不多但每条都有来历。规则一:表达式插值用花括号——<p>{count + 1}</p>,花括号里是任意 JavaScript 表达式,但不能是语句(if/for 不行)。规则二:组件必须返回单个根元素,不想多包一层 div 就用 Fragment:<></>。规则三:属性名是 camelCase——class 写 className,for 写 htmlFor(因为 class/for 是 JS 保留字),事件 onClick、onChange 同理。规则四:style 接收对象——style={{ color: 'red' }}(外层花括号是插值,内层是对象字面量)。

为什么 JSX 里不能直接写 if?因为 JSX 编译后是表达式,而 if 是语句。替代方案三个:三元表达式({ok ? <A/> : <B/>})、逻辑与({loading && <Spinner/>})、提前 return(在组件函数顶部处理分支)。逻辑与有个著名陷阱:{count && <List/>} 当 count 为 0 时会渲染出数字 0——因为 0 是假值但会被渲染。要么用布尔强转 {count > 0 && ...},要么用三元。

注释写法也不同:JSX 内部用 {/* 注释 */}。渲染 null/undefined/true/false 时 React 什么都不显示——这个特性被广泛用于条件渲染。字符串和数字原样渲染;数组会被展开渲染(列表渲染的基础,后章详述);对象不能直接渲染(会报错,因为 React 不知道你想显示对象的什么)。

示例

用纯 JS 验证 JSX 的渲染规则:什么值会被显示,什么值被跳过(在本教程构建时被真实执行):

import assert from 'node:assert/strict';

// 模拟 React 对子节点的处理规则
function renderChildren(children) {
  return children
    .flat()
    .filter((c) => c !== null && c !== undefined && typeof c !== 'boolean')
    .join('');
}

// null/undefined/布尔被跳过,0 和空字符串会被渲染
assert.strictEqual(renderChildren([null, 'a', undefined]), 'a');
assert.strictEqual(renderChildren([true, 'b', false]), 'b');
assert.strictEqual(renderChildren([0]), '0'); // 0 会被渲染!
assert.strictEqual(renderChildren(['']), '');

// 著名陷阱:count && <List> 在 count=0 时渲染出 "0"
function buggy(count) {
  return renderChildren([count && '有 ' + count + ' 条']);
}
function fixed(count) {
  return renderChildren([count > 0 && '有 ' + count + ' 条']);
}
assert.strictEqual(buggy(0), '0'); // 页面上出现一个孤零零的 0
assert.strictEqual(fixed(0), ''); // 什么都不渲染
assert.strictEqual(fixed(3), '有 3 条');

// 对象不能直接渲染
const user = { name: '小明' };
assert.throws(() => {
  if (typeof user === 'object' && user !== null) throw new Error('Objects are not valid as a React child');
}, /Objects are not valid/);

console.log('buggy(0) 渲染:', JSON.stringify(buggy(0)), '← 经典 bug');
console.log('fixed(0) 渲染:', JSON.stringify(fixed(0)), '← 正确');

JSX 中的条件渲染三板斧:

function Panel({ user, notifications }) {
  // 1. 提前 return:大分支在函数顶部处理
  if (!user) return <p>请先登录</p>;

  return (
    <section>
      {/* 2. 三元:二选一 */}
      <h1>{user.isVip ? '尊敬的会员' : '你好'}</h1>
      {/* 3. 逻辑与:可选显示(注意先转布尔) */}
      {notifications.length > 0 && <span className="badge">{notifications.length}</span>}
      <p style={{ color: 'gray' }}>class 要写成 className,style 是对象</p>
    </section>
  );
}

常见坑

  • {count && ...} 渲染出 0:数字假值会被显示,逻辑与前先保证左侧是布尔。
  • 属性写 HTML 原名:class/for 必须写 className/htmlFor,控制台有警告,别无视。
  • 花括号里写语句{if (ok) ...} 直接编译错误,改用三元或提前 return。
  • 试图渲染对象{user} 报 "Objects are not valid",渲染 user.name;调试想打印整个对象用 JSON.stringify。
  • style 传字符串style="color:red" 无效,要传对象 style={{ color: 'red' }}

小结

JSX = 表达式插值 + 单根 + camelCase 属性;条件渲染用三元/逻辑与/提前 return,警惕 0 被渲染。下一章把 UI 拆成组件并传数据:Props。