React 中的条件渲染,就是用普通的 JavaScript 决定组件返回什么:在 return 之前写 if 语句,在 JSX 里写三元运算符 cond ? a : b,或者用 cond && <Thing /> 来决定显示某个内容还是什么都不显示。不需要学习任何特殊的模板语法。
点击按钮,提示信息和按钮文字都会切换,因为每个三元运算符在下一次渲染时选中了另一个字符串。把 useState(false) 改成 useState(true),预览一开始就是已登录状态。
if 语句与提前返回
JSX 的花括号接受表达式,不接受语句,所以不能在标记里写 {if (x) ...}。但在 return 之前,你想写什么 if 都可以。当组件在某种状态下整体都不一样时(加载中、出错、为空),就为每种情况提前返回,把主要的标记留给正常情况。
每个按钮会让收件箱进入一种不同的状态,每种状态都有自己简短易读的分支。提前返回让正常流程留在最下面,不用层层嵌套。
JSX 中的三元运算符
当标记里只有一小部分变化时,行内的三元运算符能让代码保持紧凑:{isOpen ? <CloseIcon /> : <MenuIcon />}。三元运算符适合二选一。三个及以上选项的嵌套三元很快就会变得难以阅读:
// Hard to follow
{status === 'loading' ? <Spinner /> : status === 'error' ? <ErrorBox /> : <List />}
遇到这种情况,就把逻辑移到 return 之前的 if 链里(如上文所示),或者改用对象映射(见下文)。
&& 与多出来的 0
cond && <Thing /> 在 cond 为真值时渲染 <Thing />,为 false 时什么都不渲染。这是因为左边为假值时,&& 会返回左边的值,而 React 对 false、null 和 undefined 都不渲染任何内容。
陷阱在于 0 也是假值,而 React 会渲染数字。所以当计数为 0 时,count && <Badge /> 会在页面上放一个字面的 0。
没有未读消息时,第一行显示“Broken: 0”,第二行在“Fixed:”后面什么都没有。点击 New message,两行结果一致;点击 Mark all read,0 又回来了。解决办法是给 && 一个真正的布尔值:unread > 0、items.length > 0 或 Boolean(unread)。空字符串 '' 原则上也有同样的问题,但它渲染出来看不见,所以很少被注意到。
返回 null
组件可以返回 null,表示什么都不渲染。只在有内容需要提醒时才出现的警告横幅就很适合这样做:父组件始终渲染 <Warning />,由组件自己决定是否显示。
留意控制台:Warning 函数每次渲染都会打印日志,包括返回 null 的那几次。返回 null 省掉的是输出,而不是工作。它的 Hook 和副作用照样运行,所以如果一个组件开销很大,或者会启动某些东西(定时器、订阅),就应该在父组件中判断,根本不去渲染它。
把 JSX 存进变量
JSX 是一个值,所以你可以把它赋给变量,用 if 语句逐步构建,之后再放进标记里。当大布局中的某一块取决于多个条件时,这种写法很有用。
function Checkout({ cart, user }) {
let action;
if (!user) {
action = <a href="/login">Log in to check out</a>;
} else if (cart.length === 0) {
action = <p>Your cart is empty.</p>;
} else {
action = <button>Pay now</button>;
}
return (
<section>
<h2>Your cart</h2>
{action}
</section>
);
}
用对象在多个组件中选择
当需要“多选一”时,用一个把每个值映射到组件的对象,比长长的 if 链或 switch 更简洁,新增一种情况也只需一行。
TABS[tab] 选出一个组件,<Panel /> 把它渲染出来。这个变量必须以大写字母开头:JSX 会把 <panel /> 当作 HTML 标签。给 TABS 加一个 notifications 条目并配上它自己的组件,就会出现第四个按钮。
隐藏不等于不渲染
让东西消失有两种方式,它们的行为并不相同。当 show 为 false 时,show && <Counter /> 会把组件从树中移除。React 卸载它并丢弃它的状态,所以它再次出现时会从头开始。用 CSS 隐藏(style={{ display: show ? 'block' : 'none' }})则让它保持挂载,状态得以保留。
把两个计数器各点几下,取消勾选复选框,再重新勾选。第一个计数器回到了 0,第二个仍然保留着原来的数字。两种做法都没有错:对于每次都应该从空白开始的表单,移除是对的;对于需要保留滚动位置或输入内容的标签页,隐藏是对的。渲染一页讲解了 React 如何判断两次渲染之间是不是同一个组件。
React 19.2 新增了第三种选择 <Activity mode="hidden">:它像 CSS 一样隐藏子元素并保留状态,同时还会清理子元素的副作用,直到它们再次显示。
条件 props 和属性
条件不仅决定哪些元素出现。任何 prop 都可以接收计算出的值,所以通常只需在属性里写一个三元运算符或比较表达式:
<button disabled={!canSave} className={hasError ? 'field error' : 'field'}>
Save
</button>
<input aria-invalid={hasError} placeholder={user ? user.email : 'you@example.com'} />
当 prop 的值为 undefined 时,React 会完全省略这个属性,这是只在某些情况下添加属性的简洁写法:title={isTruncated ? fullText : undefined}。对于 disabled、checked 这类布尔属性,传入 true 或 false,React 就会添加或移除它们。
列表中的条件
想在列表中略去某些项,应该在 map 之前先过滤数组,而不是在 map 里返回 null。items.filter((i) => i.done).map(...) 读起来就是它做的事,列表与 key 一页用一个可运行的示例演示了这种写法。
常见问题
如何在 JSX 里写 if 语句?
不能把 if 语句放进 JSX 的花括号里,因为花括号只接受表达式。可以在 JSX 中使用三元运算符(cond ? a : b)或 &&,或者在 return 之前执行 if,把 JSX 存进一个变量。
为什么我的组件显示了一个 0?
当 count 为 0 时,count && <Badge /> 返回的就是 count 本身,而 React 会渲染数字 0。把左边写成真正的布尔值:count > 0 && <Badge />。
React 中怎样什么都不渲染?
让组件返回 null,或者在 JSX 中放 null、undefined、false 或 true。这些值 React 都不会渲染。
返回 null 的组件还会运行吗?
会。它的函数会运行,Hook 会运行,副作用也会触发。返回 null 只表示它不往页面上添加任何内容。想完全跳过这些工作,就不要在父组件中渲染它。
应该用 CSS 隐藏组件还是直接移除它?
移除(show && <Panel />)会卸载组件,它的状态被丢弃,下次出现时从头开始。用 display: none 隐藏则让它保持挂载,状态也会保留。根据状态是否需要保留来选择。