React 条件渲染该用哪种写法:三种写法的取舍与避坑

编程狮(w3cschool.cn) 2026-10-25 07:05:31 浏览数 (45)
反馈

先给结论:二选一用三元运算符,单分支显示用逻辑与的短路写法,分支超过两条或分支里含额外逻辑时用提前 return,必要时把分支拆成独立组件。三种写法没有谁更"高级",差别在分支数量、是否需要保留子组件状态、以及会不会把 0 渲染到页面上。

React 条件渲染该用哪种写法?

新手最常踩的坑不是选错写法,而是踩到逻辑与短路的返回值陷阱:当左侧是数字 0 时,0 && <Panel /> 的结果是 0,React 会把这个 0 当成文本节点渲染出来,页面上凭空多一个字符。这类问题不会报错,只会在列表长度恰好为零时冒出来。

本文基于 React 函数组件的通用行为,示例用 JSX 编写,未在本机执行的输出会明确标注。适用边界也一并写清:条件渲染伴随组件的卸载与重挂载,是否需要保留状态会反过来决定你该选哪种写法。

先看结论

条件渲染的目标是让不同状态下的画面切换既可读又不出意外取值,取舍的关键变量是分支数量。

  • 单分支显示:flag && <Item />,但左侧必须先转成布尔值,避免渲染出 0。
  • 二选一:flag ? <A /> : <B />,两个分支都要有返回值,不要留空。
  • 多分支或含前置逻辑:在组件里用提前 return,可读性最好。
  • 需要保留子组件内部状态的:不要卸载它,改为用样式隐藏或把状态提升到父级。

React 的渲染与组件体系较大,需要系统过一遍可以看 React 教程,重点看组件与列表那几章。

一、三种写法分别长什么样

条件渲染要解决的是同一份数据在不同状态下的呈现差异。把问题定死:一个订单页面,要在"加载中 / 空列表 / 有数据"三种状态之间切换。

第一种,逻辑与短路,适合"要么显示要么不显示":

function Badge({ unread }) {
  return <div>{unread > 0 && <span>有 {unread} 条未读</span>}</div>
}

注意这里写的是 unread > 0 而不是直接写 unread。原因前面说过:如果写成 unread && <span>…</span>,当 unread 为 0 时,整个表达式的值就是数字 0,页面上会出现一个孤零零的 0。改成比较表达式后左侧恒为布尔值,这个分支就安全了。

第二种,三元运算符,适合二选一:

function AuthArea({ user }) {
  return user
    ? <span>你好,{user.name}</span>
    : <a href="/login">去登录</a>
}

三元的特点是两个分支必须都给出值。偷懒写成 user ? <span>…</span> : null 是可以的,但要清楚它的含义是"否则什么都不渲染",不要留一个空分支让人猜。

第三种,提前 return,适合分支多或每个分支带前置处理:

function OrderList({ state, orders }) {
  if (state === 'loading') {
    return <Skeleton />
  }
  if (state === 'empty') {
    return <Empty text="还没有订单" />
  }
  return <ul>{orders.map((o) => <li key={o.id}>{o.title}</li>)}</ul>
}

这种写法的优势是每个分支一眼能看完,不需要在 JSX 里穿来穿去嵌套三元。

按分支数量选择条件渲染写法的判断路径

二、三个真实的坑与它们的触发条件

写条件渲染时最容易被忽略的不是语法,而是表达式求值的结果本身。下面三个坑的触发条件各不相同:

坑一:短路左侧是数字。 前面已经演示了 0 的情况。同理,空字符串、NaN 也可能带来意外渲染。稳妥做法是在左侧显式转布尔:Boolean(items.length) && <List />,或者直接用 items.length > 0。

坑二:嵌套三元导致不可读。 三元套三元在 JSX 里很难维护,超过两层就应该改成提前 return,或者把分支抽成一个返回 JSX 的函数。还有一种更整洁的写法是用对象查表:

const VIEWS = {
  loading: <Skeleton />,
  empty: <Empty />,
  error: <ErrorTips />
}

return VIEWS[state] ?? null

坑三:条件切换让子组件状态丢失。 条件从真变假时,React 会卸载对应的子树,子树内部的 useState 保存的值随之销毁。切回来时是全新的组件实例,输入框里的内容不会保留。需要保留状态时,正确做法是:(1) 用样式控制显隐而不是条件渲染;(2) 把需要保留的状态提升到父组件;(3) 给同一位置的组件保持一致的 key。

三、和 Hooks 规则的冲突边界

提前 return 有一个硬性限制:return 必须写在所有 Hook 调用之后。Hooks 要求每次渲染的调用顺序完全一致,所以下面这样写是错误的:

function Profile({ id }) {
  if (!id) {
    return <Empty />
  }
  const [name, setName] = useState('')   // 错误:Hook 在条件分支之后
  return <input value={name} onChange={(e) => setName(e.target.value)} />
}

一旦某个分支提前返回,Hook 的调用次数就会变化,React 会直接抛出渲染顺序不一致的错误。修复方式是把分支判断推到最外层:让父组件负责决定是否渲染 Profile,或者把需要 Hook 的部分拆成一个子组件,由父组件条件渲染这个子组件。

规律可以总结成一句话:用 Hook 的组件不做条件分支,做条件分支的组件不用 Hook。这条边界划清楚之后,大部分相关报错都不会出现。

运算符优先级与类型转换的细节记不清时,翻 JavaScript 参考手册 核对一遍最稳妥。

0 被渲染、嵌套三元、状态丢失三类问题对照

四、排查顺序与验证方式

把选择写成一个固定的判断流程(预期结果,未在本机执行):

  • 配置:先数分支数量,一条分支用短路,两条用三元,三条及以上提前 return;
  • 检查:全局搜索 && 前面紧跟变量名的写法,逐个确认左侧是不是布尔值;
  • 运行:把列表长度临时改成 0,看页面上有没有多出字符;
  • 预期:列表为空时页面只显示空状态组件,没有任何多余数字;
  • 失败:页面出现孤立的 0,说明短路左侧是数字,改为比较表达式或 Boolean 包裹;
  • 失败:控制台报 Hook 顺序错误,说明 return 写在了 Hook 之前,按上一节的方式拆组件;
  • 修复:需要保留输入内容的场景,改用隐藏样式或状态提升;
  • 验证:在两个分支之间来回切换三次,确认子组件状态符合预期。

性能方面不需要过度优化:条件渲染本身的开销很小,真正昂贵的是分支里组件的初始化。如果某个分支挂载很重的数据处理,把它拆成懒加载组件比在写法上纠结更有效。

总结

React 条件渲染不是语法题,而是分支数量与状态保留的选择题。

四条带走:

  • 分支数决定写法,一到三条分别对应短路、三元、提前 return;
  • 短路左侧必须转成布尔值,否则 0 会被渲染出来;
  • 条件切换会卸载子树,需要保留状态就别卸载;
  • Hook 必须写在任何分支之前,冲突时把 Hook 拆进子组件。

下一步建议清理一遍老代码里的 xxx && <Comp />,把它们统一改成布尔判断,能一次性消掉一类偶发问题。

常见问题

Q:为什么列表为空时页面上多了一个 0?

A:因为写了 list.length && <List />,长度是 0 时整个表达式的值就是数字 0,React 会把它当文本渲染。改成 list.length > 0 && 即可。

Q:三元运算符可以嵌套吗?

A:技术上可以,但超过两层可读性会迅速变差。建议改成提前 return,或者用对象把每个状态映射成一个组件。

Q:怎么用条件渲染保留输入框里的内容?

A:不要用条件卸载组件。改成给容器加隐藏样式,或者把输入框的值提升到不会被卸载的父组件里保存。

Q:为什么提前 return 之后会报 Hook 错误?

A:因为 Hook 出现在了条件分支之后,某些渲染路径下它根本没被调用。把 Hook 调用的组件和做分支判断的组件拆开就能解决。

Q:三种写法在性能上有区别吗?

A:写法本身几乎没有区别,开销主要来自被挂载组件的初始化。真正卡的时候去优化组件本身,而不是换个条件写法。

延伸学习

想把这块做扎实,可以按这个顺序来:

  1. 想补齐组件与 JSX 的基础,Web前端入门扫盲课程 可以跟一遍;
  2. 读一篇相关笔记 react渲染原理是什么,看渲染流程层面的解释;
  3. 想随手验证一段表达式,用 JavaScript 在线运行 跑一遍最快。

0 人点赞