转化率 0% 却显示"无数据":`||` 与 `??` 的经典课堂,以及什么数据该用哪个

2,585 阅读4分钟

问题场景

运营看板上线第二天,产品经理来质询:

"用户详情页,明明很多人转化率是 0%,为什么页面上显示的是'暂无数据'四个字?"

查代码,看板渲染处是:

const conversionRate = data.rate || '暂无数据'

业务逻辑是"没数据就显示暂无数据",看起来没毛病。但结果乱套了——所有转化率为 0% 的用户,都被显示成了"暂无数据"

原因:0(以及 0%)是 假值(falsy),被 || 当成了"没有数据"吞掉了。

原因分析

|| 运算符会把所有假值都当成"没有":

假值例子
''空字符串
0数字零 / 0%
false布尔假
null
undefined未定义
NaN非数字

看板场景里,转化率为 0 是货真价实的"有数据"——用户访问了但一个都没转化,0% 恰恰是最重要的运营信号。结果被 || 一口吞掉,显示成"暂无数据",让运营误判"没人访问过"。

根因:|| 把"假值"和"没有"混为一谈。 而这里的 0 是有真实业务含义的,不该被兜底。

解决方案

方案一:用 ??(空值合并运算符)—— 推荐

const conversionRate = data.rate ?? '暂无数据'

?? 只在左操作数为 nullundefined 时取右侧值。0false'' 都会被原样保留

  • 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 同时覆盖 undefinednull,不误伤 0/false。语义直白,不依赖运算符心智负担。

方案三:后端统一语义(治本)

后端约定:"没有数据"时不返回该字段(或返回 null),任何数值(含 0)都正常返回。前端只信任 ??,后端保证"假值 ≠ 缺省"。前后端边界清晰,最不易错。

核心判据:这个值到底"算不算有数据"?

|| 还是 ??,取决于"假值是否有真实含义",不是哪个运算符更高级:

场景0/false/'' 是啥该用
转化率、库存数、完成数真实数据(0 很关键)??
头像 url、昵称、占位文案空 = 该兜底默认``
开关状态 isAuthedfalse 是有效状态??(若想区分未设置)

决策速查:

| 左值是 | || 结果 | ?? 结果 | 取决于 | |---|---|---|---| | 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 不再出现。