陌上人如玉
公子世无双

1.7 条件渲染与列表渲染

1.7 条件渲染与列表渲染

一句话:条件渲染靠「表达式」(三元、逻辑与、提前 return、映射表),列表渲染靠 map + 稳定唯一的 key;要小心 && 遇到 0 会渲染出 0 的陷阱。

一、条件渲染的五种写法

// ① 三元表达式:二选一
function Status({ isOnline }) {
  return {isOnline ? '在线' : '离线'};
}

// ② 逻辑与:有或没有
function Notice({ message }) {
  return 
{message && {message}}
; } // ③ 提前 return(组件级条件,可读性最好)⭐ function UserProfile({ user, loading, error }) { if (loading) return ; if (error) return ; if (!user) return ; return ; } // ④ 变量提取(多分支场景) function Content({ status, data }) { let body; if (status === 'loading') body = ; else if (status === 'error') body = ; else if (!data.length) body = ; else body = ; return
{body}
; } // ⑤ 映射表(分支多且想写得「声明式」) const statusViews = { idle: , loading: , error: , success: , }; return
{statusViews[status]}
;

对比:

写法 适用 注意
三元 二选一、行内小片段 嵌套三层以上很难读
&& 有或没有 左侧必须是布尔值(见下节)
提前 return 组件级的多分支 逻辑清晰,推荐
变量提取 分支多且需要放在同一容器里 稍啰嗦但清晰
映射表 状态机式的场景 需要保证 status 覆盖全

二、&& 的陷阱(必考)

// ❌ count 为 0 时渲染出 0
{count && }

// ❌ 同样的问题:数组长度、字符串长度、NaN
{list.length && }          // 空数组会渲染 0
{name.length && {name}}  // 空字符串会渲染 0(len=0)
{NaN && }                     // 渲染 NaN

// ✅ 四种修复
{count > 0 && }
{!!count && }
{Boolean(count) && }
{count ?  : null}

为什么:&& 的语义是「左侧为真则返回右侧,否则返回左侧」。当左侧是 0 时,0 && X 的结果是 0,而 React 会渲染数字 0(0 是「假值但可渲染」的值)。而 false / null / undefined / true 会被 React 忽略,所以它们才是「安全」的假值。

表达式的值 React 渲染结果
false、null、undefined、true 什么都不渲染 ✅
0 渲染出 0 ⚠️
NaN 渲染出 NaN ⚠️
'' 渲染空文本(不可见,但 DOM 里有个空文本节点)
[] 什么都不渲染 ✅

最佳实践:JSX 里出现 && 时,养成左边写比较表达式的习惯(list.length > 0 && ...),而不是直接写变量。

三、三元表达式的可读性

// ❌ 嵌套三元很难读
{isLoading ?  : error ?  : data.length ?  : }

// ✅ 用提前 return 拆开
function View({ isLoading, error, data }) {
  if (isLoading) return ;
  if (error) return ;
  if (!data.length) return ;
  return ;
}

// ✅ 或提取成变量
const content = isLoading
  ? 
  : error
    ? 
    : ;

四、列表渲染

// 基本写法
function List({ items }) {
  return (
    
    {items.map((item) => (
  • {item.name}
  • // ⭐ key 在最外层元素 ))}
); }

key 的三个规则:

① 必须加在 map 回调返回的【最外层元素】上
② 必须【稳定且唯一】(同一个列表内不重复、跨渲染不变)
③ 用数据的唯一 ID(数据库主键、UUID),不要用 index
// ❌ key 加错位置
{items.map((item) => (
  
{/* 没有 key */} {/* ⚠️ 加在了内层,无效 */}
))} // ✅ 正确 {items.map((item) => (
))} // ✅ 需要包多个元素时用 Fragment + key {items.map((item) => (
{item.term}
{item.desc}
))}

用 index 作 key 的问题(详见 4.5):

// ❌ 列表会在头部插入/删除/排序时出 bug
{items.map((item, index) => )}

// 具体后果(以「删除第一项」为例):
// 原列表 [A, B, C] → key = [0, 1, 2]
// 删掉 A 后 [B, C] → key = [0, 1]
// React 认为「key 0 和 1 还在,只是内容变了」→ 复用原有的 DOM 与状态
// → 如果 Row 里有输入框、展开状态、动画,就会出现「内容错位」或「状态残留」
场景 用 index 的后果
列表只读不变 没问题(可接受)
头部增删 后续所有项的内容错位、状态残留
排序 状态跟着位置走而不是跟着数据走
有内部状态的项(输入框、勾选、展开态) 状态与数据错误对应
// 确实没有唯一 ID 时的三种处理
// ① 生成稳定 ID(数据入库时分配)
// ② 用「内容 + 索引」拼一个相对稳定的 key(仍有风险)
key={`${item.name}-${item.type}`}
// ③ 列表确实只读且永不重排 → 可以用 index(但要在注释里说明原因)

五、空列表与边界状态

function ItemList({ items, loading, error }) {
  // ① 加载中
  if (loading) return ;

  // ② 错误
  if (error) return ;

  // ③ 空数据(容易被忽略)
  if (!items.length) return ;

  // ④ 正常渲染
  return (
    
    {items.map((it) =>
  • {it.name}
  • )}
); }
// 搜不到结果的空状态(与「没有数据」不同)
{keyword && !results.length && (
  没有找到与「{keyword}」相关的结果
)}

四种状态要完整:loading / error / empty / success。实际项目里「空状态」最常被遗漏,导致用户看到一片空白以为页面坏了。

六、列表渲染的进阶技巧

// ① 渲染对象(不能直接 map)
{Object.entries(userMap).map(([id, user]) => (
  
))}

// ② 渲染指定数量的占位元素
{Array.from({ length: 5 }, (_, i) => )}

// ③ 条件过滤后再渲染
{items
  .filter((it) => it.visible)
  .map((it) => )}

// ⚠️ 但过滤/排序最好用 useMemo(避免每次渲染都重算)
const visible = useMemo(
  () => items.filter((it) => it.visible).sort((a, b) => a.order - b.order),
  [items]
);
{visible.map((it) => )}

// ④ 在列表项上复用稳定的事件处理函数(配合 memo)
const handleDelete = useCallback((id: string) => {
  setItems((prev) => prev.filter((it) => it.id !== id));
}, []);

// ✅ 用 data-* 传参 → 处理函数引用稳定 → 子组件可以 memo
    { const id = (e.target as HTMLElement).closest('[data-id]')?.getAttribute('data-id'); if (id) handleDelete(id); }}> {items.map((it) => (
  • {it.name}
  • ))}
// ⑤ 长列表的性能:虚拟滚动(见 5.5 与 11.6)
// 渲染 10000 个 DOM 节点会很慢,只渲染可视区域内的元素

// ⑥ 大数据量的列表用「分段渲染」
const [visibleCount, setVisibleCount] = useState(50);
const visible = items.slice(0, visibleCount);
// 滚动到底部时 setVisibleCount((c) => c + 50)

七、常见坑

// ① && 遇到 0 / NaN
{count && }                  // ❌
{list.length && }            // ❌

// ② key 缺失或写错位置
{items.map((it) => )}            // ⚠️ 控制台警告
{items.map((it) => 
)} // ⚠️ 位置错误 // ③ 用 index 作 key {items.map((it, i) => )} // ⚠️ 列表变动时出 bug // ④ 在 map 里用 {} 忘了 return {items.map((it) => { })} // ❌ 返回 undefined {items.map((it) => { return ; })}// ✅ {items.map((it) => )} // ✅ // ⑤ 直接渲染数组里的对象 {items.map((it) =>
  • {it}
  • )} // ❌ 如果 it 是对象则报错 // ⑥ 漏掉空状态 {items.map(...)} // ⚠️ 空数组时页面一片空白 // ⑦ 在渲染中做过滤/排序 {items.filter(...).sort(...).map(...)} // ⚠️ 每次渲染都重算(用 useMemo) // ⑧ 嵌套列表的 key 冲突
      {groups.map((g) => (
    • {g.items.map((it) => )} // ✅ 只要求「同层唯一」
    • ))}
    // ⑨ 条件渲染导致组件「卸载重建」 {isVisible && } // isVisible 变化时 Child 会卸载/挂载 → 内部状态丢失 // ✅ 需要保留状态时用 CSS 隐藏或 v-show 等价方案

    面试延伸

    1. 「React 里怎么做条件渲染?」

    五种方式:① 三元表达式(二选一);② 逻辑与 &&(有或没有);③ 提前 return(组件级的多分支,可读性最好);④ 变量提取;⑤ 映射表(状态机式场景)。核心原因是 JSX 的 { } 里只能写表达式,所以不能用 if / for 语句。

    1. 「{count && <X />} 有什么问题?」

    当 count 为 0 时,0 && <X /> 的结果是 0,而 React 会把数字 0 渲染成文本,于是页面出现一个 0。根因是「0 是假值但可渲染的值」,而 false/null/undefined 才是「会被忽略的假值」。修复方式是让左侧变成布尔值:count > 0 && ...、!!count && ... 或改用三元。

    1. 「哪些值会被 React 忽略、哪些会被渲染?」

    会被忽略:false、true、null、undefined、空数组。 会被渲染:字符串、数字(含 0 与 NaN)、元素、数组。 会报错:普通对象(Objects are not valid as a React child)、函数(除非作为渲染函数传递)。 这个规则是「&& 陷阱」与「为什么不渲染布尔值」的根源。

    1. 「key 的作用是什么?为什么不能用 index?」

    key 是 React 用来判断「新旧列表中的元素是否是同一个」的身份标识,它决定了组件是复用还是重建。用 index 时,列表头部插入/删除/排序会导致「key 对应的数据变了但 key 没变」,React 于是复用错误的 DOM 与组件状态——表现为输入框内容错位、展开状态残留、动画异常。所以只要列表会变动,就必须用稳定唯一 ID。

    1. 「渲染列表时最容易犯的错误有哪些?」

    ① key 缺失(控制台警告 + 性能问题);② key 加在 map 返回的内层元素上(无效);③ 用 index 作 key;④ map 回调用了 { } 但忘记 return;⑤ 在渲染中做 filter / sort(每次渲染都重算,应 useMemo);⑥ 漏掉空状态;⑦ 直接渲染对象(报错);⑧ 内联箭头函数导致 memo 失效。

    1. 「列表的四种状态是什么?为什么容易出错?」

    loading(加载中)、error(失败)、empty(成功但无数据)、success(有数据)。其中 empty 最容易被遗漏——开发者往往只处理了「有数据」和「加载中」,导致搜索无结果或首次进入时页面一片空白,用户以为页面坏了。除了 UI,还要给 empty 状态提供引导(如「去创建第一条」的按钮)。

    1. 「{isVisible && <Child />} 会有什么副作用?」

    当 isVisible 从 true 变 false 再变回 true 时,<Child /> 会被卸载再重新挂载,因此它的内部 state、滚动位置、输入内容、动画进度都会重置。如果需要「隐藏但保留状态」,应该改用 CSS 隐藏(display: none / visibility: hidden)或用 hidden 属性,而不是条件渲染。

    1. 「长列表怎么优化?」

    分三层:① key 用稳定 ID(让 React 能正确复用);② 行组件用 React.memo 并保证 props 引用稳定(处理函数用 useCallback 或用 data-* + 事件委托);③ 虚拟滚动——只渲染可视区域内的元素,用占位元素撑起总高度(1 万条数据能把 DOM 节点从 1 万降到几十个)。此外「分页」与「分段加载」也是很实用的手段。

    一句话速记

    条件渲染用表达式:三元(二选一)、&&(有或没有)、提前 return(最清晰)、变量、映射表;{count && <X/>} 在 count === 0 时会渲染出 0(左侧要写比较表达式,因为 0/NaN 会被渲染、false/null/undefined 会被忽略);列表必须用 map + 稳定唯一 key,且 key 加在最外层元素上,不要用 index;四种状态(loading/error/empty/success)要写全,empty 最常漏;条件渲染会卸载重建(需要保留状态就用 CSS 隐藏)。

    赞(0) 打赏
    未经允许不得转载:陌上寒 » 1.7 条件渲染与列表渲染

    评论 抢沙发

    觉得文章有用就打赏一下文章作者

    非常感谢你的打赏,我们将继续给力更多优质内容,让我们一起创建更加美好的网络世界!

    微信扫一扫

    支付宝扫一扫