Skip to content
This page has been auto-translated and may contain errors.View in English

条件渲染

已登录的访客看到一个仪表板,未登录的访客看到登录表单,而失败的请求需要在页面某处显示错误消息。一个像 isLoggedIn 这样的布尔值通常存在于状态中,变化的是根据当前值渲染哪段 UI。JSX 是 JavaScript,所以决定渲染什么和代码中其他决定的工作方式是一样的:在 return 语句中对一个表达式求值,用它来选择一个值。

用三元运算符在两个元素之间选择

当一段 UI 只有两种可能时,用三元运算符来选择:

jsx
return (
  <div>
    {isLoggedIn ? <Dashboard /> : <Login />}
    {hasError && <p>Something went wrong</p>}
  </div>
)

isLoggedIn ? <Dashboard /> : <Login /> 的读法和其他三元运算符一样:如果 isLoggedIn 为真,这个表达式就计算为 <Dashboard />,否则计算为 <Login />。无论它计算为什么,那个值就被渲染在 JSX 中的那个位置。花括号的作用是让你在标记中间插入一个 JavaScript 表达式,三元运算符就是一个普通的表达式。

用 && 显示或隐藏一个元素

第二行处理的是另一种决策形式:显示某样东西,或者什么都不显示。hasError && <p>Something went wrong</p>&& 运算符的方式和它在 JavaScript 其他地方的工作方式完全一样。如果 hasErrorfalse&& 会短路,整个表达式计算为 false,根本不会执行右边的 JSX。如果 hasErrortrue,表达式计算为那个 <p> 元素。

这之所以能正确渲染,归结于 React 如何处理结果。React 对 falsenullundefined 不渲染任何内容,所以当 hasErrorfalse 时,页面上完全不会显示任何东西。

falsy 值的坑

&& 不只会产生 truefalse。像任何使用 && 的 JavaScript 表达式一样,它计算为落脚的那一侧,那一侧可以是任何值,不一定是布尔值。大多数时候这没问题,但当左侧是数字时就会变成 bug:

jsx
{count && <Badge />}
// count = 0 → 在页面上渲染 "0"

如果 count0,这个表达式计算为 0。React 对 falsenullundefined 不渲染,但 0 是一个真实的、可渲染的值,所以 React 会把它放在页面上。徽章不显示,但一个多余的 0 显示了,就在你期望什么都不显示的地方。

解决办法是确保 && 左侧始终是真正的布尔值:

jsx
{count > 0 && <Badge />}
// count = 0 → 什么都不渲染

count > 0 总是计算为 truefalse,所以表达式要么渲染徽章,要么什么都不渲染,没有多余的 0 留下来。

完全不渲染任何东西

有时一个组件没有东西要显示,最清楚的方式就是提前返回:

jsx
function Banner({ message }) {
  if (!message) {
    return null
  }

  return <p className="banner">{message}</p>
}

message 为空时,函数在构建 JSX 的其余部分之前就返回了 null。这样做让主 return 专注于有东西要渲染的情况,而不是用一个更多的条件包裹整个 JSX。

选择用哪个工具取决于条件在选择什么。两个元素:用三元运算符。一个元素或什么都不是:用 &&。一个组件没有东西要渲染:在主 return 之前用 if (!x) return null 提前返回,而不是用一个条件包裹整个 JSX 树。把它们混在一起,用三元运算符来做本应用 && 的事,或在三元运算符里面埋一个提前返回,通常是要改用更直接工具的信号。

{count && <Badge />} 学到的 falsy 坑不仅限于 0NaN 也会以同样的方式渲染,所以 {total / count && <Badge />}count0 时能在页面上打印 NaN。空字符串 "" 是这个 bug 更安静的版本:{name && <Greeting name={name} />}name"" 时什么都不显示,因为页面上的空字符串是看不见的,但它仍然是一个多余的文本节点坐在 DOM 中。解决办法是一样的:用比较像 count > 0 的东西或 Boolean(...) 调用来强制左侧为显式布尔值,而不是指望原始值以你期望的方式是 falsy。

一旦有超过一个条件在起作用,条件就变得难读起来。一个三元运算符嵌套在另一个三元运算符里面在 JSX 中是条件已经超出内联逻辑范围的第一个信号:

jsx
{status === 'loading' ? <Spinner /> : status === 'error' ? <ErrorMessage /> : <Content />}

有两个办法来整理这个。把决策拉到 return 前面的一个变量里,这样 JSX 只需要嵌入结果:

jsx
const view =
  status === 'loading' ? <Spinner /> :
  status === 'error' ? <ErrorMessage /> :
  <Content />

return <div>{view}</div>

或把逻辑抽取到一个小的辅助函数里,它返回正确的元素,一旦有两三个以上的分支,这样读起来会更好。无论哪种方式,目标是一样的:让 JSX 本身不包含决策,让它嵌入已经在上面决定好的值。

从一个组件返回 null 什么都不渲染:没有为它创建 DOM 节点,甚至没有空的。它是一个组件的合法返回值,React 把 null 返回当作和返回空片段的方式一样。

一旦你开始在同一个位置替换组件进出,还有第二件值得了解的事。React 通过逐位置地遍历树来协调树,在每个位置它比较现在在那里的元素类型和上次渲染时那里的类型。

jsx
{isEditing ? <EditForm /> : <ViewForm />}

isEditing 翻转时,EditFormViewForm 是占据同一位置的不同组件类型,所以 React 卸载旧的,从头挂载新的。比如说用户正在输入的输入值这样的 EditForm 原本保持的任何状态,在 ViewForm 取代它的那一刻就消失了。这和在同一位置用不同的 props 渲染同一个组件类型不同:同一位置的同一类型意味着 React 更新现有实例,它的状态存活下来。类型决定了 React 是看到"同一样东西,更新了"还是"一个全新的东西"。

Juno决定显示什么是普通的 JavaScript 三元运算符在两个元素间选择,&& 显示一个元素或什么都不显示,从一个组件返回 null 是你说"这里没有东西要渲染"的方式。当 count 可能是 0 时要当心 {count && <Badge />},因为 0 是 React 会真正打印的值。改写成 {count > 0 && <Badge />} 能让左侧保持一个真实的布尔值。
Juno决定显示什么是普通的 JavaScript 当你在两个元素间选择时用三元运算符,当你在一个元素和什么都不是间选择时用 &&。用 && 要记住的坑是它计算为落脚的那一侧,所以像 0 这样的 falsy 数字会被渲染成它本身而不是消失。通过让左侧成为显式布尔值来保护,像 count > 0,当一个组件没有东西要显示时用一个提前 return null
Juno决定显示什么是普通的 JavaScript 这里的每个条件渲染技巧都是在 JSX 内求值的普通 JavaScript,唯一 React 特定的部分是 falsenullundefined 渲染为什么都不是,而包括 0 在内的每个其他值都渲染成它本身。这就是 {count && <Badge />} 的陷阱和 {count > 0 && <Badge />} 的修复。值得往前带的部分是按位置和类型的协调:在同一位置交换进去一个不同的组件类型,它的状态就消失了,因为 React 看到的是一个新元素,不是对旧元素的更新。

接下来:表单,你会用状态来处理输入字段。