问题场景
运营看板上线第二天,产品经理来质询:
"用户详情页,明明很多人转化率是 0%,为什么页面上显示的是'暂无数据'四个字?"
查代码,看板渲染处是:
const conversionRate = data.rate || '暂无数据'
业务逻辑是"没数据就显示暂无数据",看起来没毛病。但结果乱套了——所有转化率为 0% 的用户,都被显示成了"暂无数据"。
原因:0(以及 0%)是 假值(falsy),被 || 当成了"没有数据"吞掉了。
原因分析
|| 运算符会把所有假值都当成"没有":
| 假值 | 例子 |
|---|---|
'' | 空字符串 |
0 | 数字零 / 0% |
false | 布尔假 |
null | 空 |
undefined | 未定义 |
NaN | 非数字 |
看板场景里,转化率为 0 是货真价实的"有数据"——用户访问了但一个都没转化,0% 恰恰是最重要的运营信号。结果被 || 一口吞掉,显示成"暂无数据",让运营误判"没人访问过"。
根因:|| 把"假值"和"没有"混为一谈。 而这里的 0 是有真实业务含义的,不该被兜底。
解决方案
方案一:用 ??(空值合并运算符)—— 推荐
const conversionRate = data.rate ?? '暂无数据'
?? 只在左操作数为 null 或 undefined 时取右侧值。0、false、'' 都会被原样保留:
rate = 0→ 显示0%✅(正确!0% 是真实数据)rate = undefined(接口没返回该字段)→ 显示"暂无数据" ✅rate = null→ 显示"暂无数据" ✅
注意 ?? 不能和 &&/|| 混用而不加括号:
// ❌ 语法错误
const a = b ?? c || d
// ✅ 加括号
const a = (b ?? c) || d
方案二:显式判断(最保守,可读性最好)
const conversionRate = data.rate != null ? data.rate : '暂无数据'
!= null 同时覆盖 undefined 和 null,不误伤 0/false。语义直白,不依赖运算符心智负担。
方案三:后端统一语义(治本)
后端约定:"没有数据"时不返回该字段(或返回 null),任何数值(含 0)都正常返回。前端只信任 ??,后端保证"假值 ≠ 缺省"。前后端边界清晰,最不易错。
核心判据:这个值到底"算不算有数据"?
选 || 还是 ??,取决于"假值是否有真实含义",不是哪个运算符更高级:
| 场景 | 0/false/'' 是啥 | 该用 | ||
|---|---|---|---|---|
| 转化率、库存数、完成数 | 真实数据(0 很关键) | ?? | ||
| 头像 url、昵称、占位文案 | 空 = 该兜底默认 | ` | ` | |
开关状态 isAuthed | false 是有效状态 | ??(若想区分未设置) |
决策速查:
| 左值是 | || 结果 | ?? 结果 | 取决于 |
|---|---|---|---|
| null/undefined | 右侧 | 右侧 | 等价,都兜底 |
| 0 | 右侧(被吞)| 保留 0 | 0 是否合法数据 |
| '' | 右侧(被吞)| 保留 '' | 空串是否有语义 |
| false | 右侧(被吞)| 保留 false | false 是否有语义 |
一句话:?? 只把 null/undefined 当"没有";|| 把所有假值当"没有"。当 0/false/空串是合法数据时用 ??,当它们该兜底时用 ||。
还有哪些容易踩的同类坑
1. 完成数(0 题)
const count = data.completedCount || '无数据' // 完成 0 题 → 错显示"无数据" ❌
const count = data.completedCount ?? '无数据' // ✅ 0 题 → 显示 0
2. 布尔开关
const authed = data.isAuthed ?? false // undefined → false(未设置当未认证)
// 若用 || 且 data.isAuthed 是 false,结果也是 false(碰巧对)
// 但若想区分"未设置 vs 明确 false",?? 语义更准
3. 函数默认参数(易混淆,但行为类似 ??)
function setTheme(color = '#333') {}
setTheme('') // '' 不是 undefined → color = ''(不触发默认)
setTheme(undefined) // → color = '#333'(触发默认)
函数默认参数只对 undefined 生效,和 ?? 一致,不是 ||。
4. 反例:空串就该兜底(这时 || 才对)
// 用户没填昵称时兜底"游客"
const nickname = user.nickname || '游客' // ✅ 空串 → 游客
const nickname = user.nickname ?? '游客' // 空串被保留 → 显示空昵称 ❌(未必想要)
这是 || 的正确用法:空串在这里就是"没填",该兜底。
要点总结
| 运算符/语法 | 兜底条件 | 适用 | ||
|---|---|---|---|---|
?? | 仅 null/undefined | 假值(0/false/'')是合法数据时 | ||
| ` | ` | 所有假值 | 假值就该兜底默认时 | |
| 函数默认参数 | 仅 undefined | 参数缺省 |
黄金法则:
用之前先问:"这个
0/false/空串,算'有数据'还是'没数据'?"
- 算"有"(要保留)→
??- 算"没有"(该兜底)→
||
头像场景的正解(很多人误用):如果头像 url 为空串,UI 本来就该显示默认头像(空串=没有可用图)——所以用 || 是正确的,不是 bug。真正要区分"没上传 vs 上传失败"是后端语义的事,不该指望前端 ?? 解决。
工程防护
代码里容易无脑用 || 兜底,建议加规则 + 约定:
// .eslintrc.json
{
"rules": {
"@typescript-eslint/prefer-nullish-coalescing": "error"
}
}
prefer-nullish-coalescing 会在你"本应兜底缺省却用了 ||"(可能误吞 0/false)时提示改用 ??。
再配一条硬性实践:
code review checklist:遇到
||接一个可能为假的变量,先问"这个 0/false/空串是不是合法数据?"
从此,转化率 0% 显示"无数据"这类 bug 不再出现。