聊天讨论 别再这样写条件渲染了——你的 React 组件里藏着这 5 种定时炸弹

193577746(kyriewen) · August 05, 2026 · 8 hits

你的 React 组件里一定有条件渲染。

{condition && <Component />}、三元表达式、if/else——这些你每天都在写。

但这些写法里有 5 种坑,不出问题的时候一切正常,一旦触发就是线上事故。我在生产环境里被炸了不止一次。

炸弹 1:&& 短路渲染出一个"0"

// ❌ 你以为它不会渲染,但它渲染了一个 "0"
function NotificationBadge({ count }) {
  return (
    <div>
      {count && <span className="badge">{count}</span>}
    </div>
  );
}

// 当 count = 0 时,页面上会出现一个孤零零的 "0"

为什么? && 的短路规则:左侧是 falsy 就返回左侧的值0 是 falsy,所以返回 0。React 会把数字 0 渲染到 DOM 里。

同样的坑还有 "" 空字符串——不会渲染,但 NaN 会渲染出 "NaN"

// ✅ 显式转为布尔值
{count > 0 && <span className="badge">{count}</span>}

// 或者用三元
{count ? <span className="badge">{count}</span> : null}

// 通用安全写法:!!
{!!items.length && <List items={items} />}

原则:&& 左侧必须是布尔表达式,永远不要放数字或可能为 0 的变量。

炸弹 2:三元嵌套超过两层

// ❌ 三个月后你自己都看不懂
return (
  <div>
    {isLoading
      ? <Spinner />
      : error
        ? <ErrorMessage error={error} />
        : data
          ? data.items.length > 0
            ? <List items={data.items} />
            : <EmptyState />
          : null}
  </div>
);

这段代码现在能跑。但下周产品说"加载失败要有重试按钮",你要在哪层加?哪层是 error 的分支?看 3 分钟才能确认。

// ✅ 早返回 + 分离
function DataView({ isLoading, error, data }) {
  if (isLoading) return <Spinner />;
  if (error) return <ErrorMessage error={error} />;
  if (!data || data.items.length === 0) return <EmptyState />;

  return <List items={data.items} />;
}

原则:三元表达式最多嵌套一层。超过一层就拆成 if 早返回或抽成子组件。

炸弹 3:条件渲染导致组件状态丢失

// ❌ 切换tab后表单输入全丢了
function TabPanel({ activeTab }) {
  return (
    <div>
      {activeTab === 'form' && <UserForm />}
      {activeTab === 'preview' && <Preview />}
    </div>
  );
}

用户在表单里填了一堆数据,切到 Preview 看看效果,切回来——表单空了。

因为 && 条件为 false 时组件被卸载了,所有内部 state 都丢了。

// ✅ 方案1:用 CSS 隐藏而不是卸载
function TabPanel({ activeTab }) {
  return (
    <div>
      <div style={{ display: activeTab === 'form' ? 'block' : 'none' }}>
        <UserForm />
      </div>
      <div style={{ display: activeTab === 'preview' ? 'block' : 'none' }}>
        <Preview />
      </div>
    </div>
  );
}

// ✅ 方案2:状态提升到父组件
function TabPanel({ activeTab }) {
  const [formData, setFormData] = useState({});

  return (
    <div>
      {activeTab === 'form' && (
        <UserForm data={formData} onChange={setFormData} />
      )}
      {activeTab === 'preview' && <Preview data={formData} />}
    </div>
  );
}

原则:条件渲染 = 卸载/重新挂载。如果组件有用户输入或内部状态,要么用 CSS 隐藏,要么把状态提升。

炸弹 4:用 useEffect + state 做"派生"条件

// ❌ 用useEffect同步计算一个本该直接派生的值
function ProductList({ products, category }) {
  const [filteredProducts, setFilteredProducts] = useState([]);

  useEffect(() => {
    setFilteredProducts(products.filter(p => p.category === category));
  }, [products, category]);

  return (
    <div>
      {filteredProducts.length > 0
        ? <List items={filteredProducts} />
        : <EmptyState />}
    </div>
  );
}

看着好像没问题。但这里有两个 bug

  1. 首次渲染闪烁:组件先渲染一次空列表(useState([])),然后 useEffect 执行后触发第二次渲染。用户会看到 EmptyState 闪一下。
  2. 不必要的渲染:每次 products/category 变化会触发两次渲染——一次正常渲染、一次 useEffect 触发的 setState。
// ✅ 直接计算,不要"同步"
function ProductList({ products, category }) {
  const filteredProducts = products.filter(p => p.category === category);

  return (
    <div>
      {filteredProducts.length > 0
        ? <List items={filteredProducts} />
        : <EmptyState />}
    </div>
  );
}

没有 state,没有 effect,没有闪烁,没有多余渲染。 如果 filter 逻辑很重,用 useMemo——但别用 useEffect + setState

原则:能从 props/state 直接算出来的值,就直接算。不要用 useEffect "同步"到另一个 state。

(这个也是我之前写过的 useEffect 反模式之一——但在条件渲染场景中特别容易犯。)

炸弹 5:key 相同导致条件切换时状态"串台"

// ❌ 两个输入框共享了同一个DOM,状态互相污染
function AuthForm({ mode }) {
  return (
    <div>
      {mode === 'login'
        ? <input placeholder="用户名" />
        : <input placeholder="邮箱" />}
    </div>
  );
}

用户在登录模式输入了用户名,切到注册模式——邮箱输入框里出现了刚才输入的用户名。

原因:React 的 reconciliation 认为这是同一个 <input> 节点(相同位置、相同类型),所以复用 DOM,不重置 state。

// ✅ 给不同分支加不同的 key,强制React重建
function AuthForm({ mode }) {
  return (
    <div>
      {mode === 'login'
        ? <input key="login" placeholder="用户名" />
        : <input key="register" placeholder="邮箱" />}
    </div>
  );
}

key 不同 → React 销毁旧节点、创建新节点 → state 不会串台。

// 复杂场景:整个表单组件切换
{mode === 'login'
  ? <LoginForm key="login" />
  : <RegisterForm key="register" />}

原则:当条件渲染的两个分支是"不同的东西"但 DOM 结构相似时,必须加不同的 key 防止状态串台。

速查表

定时炸弹 触发条件 修复
&& 渲染 "0" 左侧是数字/NaN 改用 > 0!!
三元嵌套 超过 2 层 拆成 if 早返回
状态丢失 条件 false=卸载 CSS 隐藏或状态提升
派生条件用 effect useEffect+setState 直接计算/useMemo
key 串台 同位置同类型 DOM 加不同 key

你踩过哪颗?

这 5 个里面,我在生产环境踩过 3 颗。第 1 颗(&&渲染 0)和第 5 颗(key 串台)是线上 bug 排查了半小时才定位到的——因为本地测试数据永远不为 0,永远不切换模式

条件渲染看着简单,但它直接关系到 React 的渲染机制、reconciliation 算法、组件生命周期。写错了不会报错,只会在某个边界条件下默默炸给用户看。

你踩过哪颗炸弹?评论区说说。

No Reply at the moment.
You need to Sign in before reply, if you don't have an account, please Sign up first.