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 时,页面上什么都不会显示。

假值陷阱

&& 不仅仅产生 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 能专注于确实有东西要渲染的情况,而不是把整个东西包裹在又一层条件中。

选择哪个工具取决于条件在什么之间选择。两个元素:用三元运算符。一个元素或什么都没有:用 &&。一个组件什么都不需要渲染:在主 return 之前用 if (!x) return null 提前返回,而不是把整个 JSX 树包裹在又一层条件中。混合使用它们,比如在应该用 && 的地方用三元运算符,或者把提前返回埋在三元运算符内,通常表示应该换用更直接的工具。

{count && <Badge />} 中的假值陷阱不仅限于 0NaN 的渲染方式一样,所以 {total / count && <Badge />}count0 时会在页面上打印 NaN。空字符串 "" 是这个 bug 的更安静的版本:{name && <Greeting name={name} />}name"" 时不会显示任何可见的东西,因为页面上的空字符串是不可见的,但它仍然是 DOM 中的一个多余的文本节点。修复方法和用于 0 的一样:把左侧强制转换为显式布尔值,用像 count > 0 这样的比较或 Boolean(...) 调用,而不是信任原始值会以你期望的方式为假。

一旦涉及多个条件,条件就会变得难以阅读。在 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 返回和空 Fragment 的处理方式一样。

一旦你开始在同一个位置交换组件,还有第二件值得了解的事情。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 这样的假值数字会被自己渲染而不是消失。通过让左侧成为显式布尔值来保护自己,比如 count > 0,当组件什么都不需要显示时用提前的 return null

Juno决定显示什么是普通的 JavaScript 这里的每个条件渲染技巧都是在 JSX 中求值的普通 JavaScript,唯一 React 特有的部分是 falsenullundefined 渲染为空内容,而其他每个值(包括 0)都会自己渲染。这就是 {count && <Badge />} 为什么是陷阱,{count > 0 && <Badge />} 才是修复。

值得往前推进的部分是按位置和类型的调和:在同一位置交换进不同的组件类型,它的状态就消失了,因为 React 看到的是一个新元素,不是对旧元素的更新。

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