
一句话:条件渲染靠「表达式」(三元、逻辑与、提前 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 等价方案
面试延伸
- 「React 里怎么做条件渲染?」
五种方式:① 三元表达式(二选一);② 逻辑与 &&(有或没有);③ 提前 return(组件级的多分支,可读性最好);④ 变量提取;⑤ 映射表(状态机式场景)。核心原因是 JSX 的 { } 里只能写表达式,所以不能用 if / for 语句。
- 「
{count && <X />}有什么问题?」
当 count 为 0 时,0 && <X /> 的结果是 0,而 React 会把数字 0 渲染成文本,于是页面出现一个 0。根因是「0 是假值但可渲染的值」,而 false/null/undefined 才是「会被忽略的假值」。修复方式是让左侧变成布尔值:count > 0 && ...、!!count && ... 或改用三元。
- 「哪些值会被 React 忽略、哪些会被渲染?」
会被忽略:false、true、null、undefined、空数组。 会被渲染:字符串、数字(含 0 与 NaN)、元素、数组。 会报错:普通对象(Objects are not valid as a React child)、函数(除非作为渲染函数传递)。 这个规则是「&& 陷阱」与「为什么不渲染布尔值」的根源。
- 「
key的作用是什么?为什么不能用 index?」
key 是 React 用来判断「新旧列表中的元素是否是同一个」的身份标识,它决定了组件是复用还是重建。用 index 时,列表头部插入/删除/排序会导致「key 对应的数据变了但 key 没变」,React 于是复用错误的 DOM 与组件状态——表现为输入框内容错位、展开状态残留、动画异常。所以只要列表会变动,就必须用稳定唯一 ID。
- 「渲染列表时最容易犯的错误有哪些?」
① key 缺失(控制台警告 + 性能问题);② key 加在 map 返回的内层元素上(无效);③ 用 index 作 key;④ map 回调用了 { } 但忘记 return;⑤ 在渲染中做 filter / sort(每次渲染都重算,应 useMemo);⑥ 漏掉空状态;⑦ 直接渲染对象(报错);⑧ 内联箭头函数导致 memo 失效。
- 「列表的四种状态是什么?为什么容易出错?」
loading(加载中)、error(失败)、empty(成功但无数据)、success(有数据)。其中 empty 最容易被遗漏——开发者往往只处理了「有数据」和「加载中」,导致搜索无结果或首次进入时页面一片空白,用户以为页面坏了。除了 UI,还要给 empty 状态提供引导(如「去创建第一条」的按钮)。
- 「
{isVisible && <Child />}会有什么副作用?」
当 isVisible 从 true 变 false 再变回 true 时,<Child /> 会被卸载再重新挂载,因此它的内部 state、滚动位置、输入内容、动画进度都会重置。如果需要「隐藏但保留状态」,应该改用 CSS 隐藏(display: none / visibility: hidden)或用 hidden 属性,而不是条件渲染。
- 「长列表怎么优化?」
分三层:① 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 隐藏)。



最新评论
读过书不知道欧·亨利的人少。教科书上选文有
这小生活不错呀
不错,必须顶一下!
看着你还在坚持,很好
看来忙了也没时间更新博客了
NIce。学习了。。。。
网站不错!!!!
简洁实用,好文章!