Vue 3 严格模式下,可选接口字段为什么会炸:一次上架页 TypeScript 复盘

前端经常会遇到展示文案涉及字符串拼接,例如「更新频率」「数据规模」等字段。JavaScript 里 a + b 很常见;打开 TypeScript 严格检查后,同一行会报「可能为未定义」。根因不是业务写错,而是接口类型标了可选,运行时拼接却当必填字符串用。

背景

catalogData 的类型是 API.IPutawayProductDetailRes。其中这些字段都带 ?:

ts 复制代码
dataSize?: string;
dataSizeUnit?: string;
updateFrequency?: string;
updateFrequencyUnit?: string;
personalInformation?: number;
authorize?: string;

? 的意思是:值类型是 T | undefined。解构后,变量在类型上就是可选的,不能当「一定存在的 string」做运算。


问题 1:可选字符串直接 + 拼接

报错

"updateFrequencyUnit"可能为"未定义"。

"dataSize"可能为"未定义"。

出错写法

ts 复制代码
updateFrequencyStr: updateFrequency + updateFrequencyUnit
dataSizeStr: dataSize + dataSizeUnit

+ 在两端都是 string 时才是拼接。string | undefined 不能直接参与,因为 undefined + '次' 在类型上不合法(运行时还会拼出 "undefined次")。

修法

用空串兜底,再模板拼接:

ts 复制代码
updateFrequencyStr: `${updateFrequency ?? ''}${updateFrequencyUnit ?? ''}`
dataSizeStr: `${dataSize ?? ''}${dataSizeUnit ?? ''}`

?? 只在 null / undefined 时替换,不会把合法的 0 吃掉。展示层已有 || '-',两边都空时仍显示 -。

不要用 ! 断言(updateFrequencyUnit!)来消错:类型过了,接口没回这个字段时仍可能拼出错误文案。


问题 2:函数参数类型比真实字段窄

报错

类型"string | undefined"的参数不能赋给类型"number | undefined"的参数。

出错写法

ts 复制代码
function getIsCover(val?: number): string {
  return val === 1 ? '是' : '否';
}

personalInformation: getIsCover(personalInformation) // number | undefined,OK
authorize: getIsCover(authorize)                      // string | undefined,不匹配

接口里「是否」并不统一:personalInformation 是 number,authorize 是 '0' | '1' 这种字符串。函数只收 number,调用就被拦下。

修法

把入参放宽,并把 '1' 和 1 都当成「是」:

ts 复制代码
function getIsCover(val?: number | string): string {
  return val === 1 || val === '1' ? '是' : '否';
}

这是对齐真实契约 ,不是关掉检查。后端 0/1 和 '0'/'1' 混用时,只认一种会在运行时显示错。


问题 3:解构了一个类型上不存在的字段

报错

类型"IPutawayProductDetailRes"上不存在属性"resourceNames"。

组件按 resourceNames 拼「关联数据资源」,但类型里对应的是 resourceId?: string[]。从 props.catalogData 解构 resourceNames,在严格检查下就是「对象上没有这个键」。

修法

先确认键是否存在、值是否为数组,再 join:

ts 复制代码
resourceNamesStr:
  'resourceNames' in props.catalogData && Array.isArray(props.catalogData.resourceNames)
    ? props.catalogData.resourceNames.join(',')
    : '-'

'x' in obj 会把后续访问收窄。长期更干净的做法是:类型补上 resourceNames,或展示改用已声明的 resourceId。


问题 4:字典 Ref 可能为 undefined

报错

"deliveryTypeOptions.value"可能为"未定义"。

useDict 返回的是 Ref<T[] | undefined>,首屏可能还没拉到字典。.value.find(...) 默认假定 .value 一定是数组。

修法

可选链:

ts 复制代码
deliveryTypeOptions.value?.find((item) => item.value === formData.value.deliveryType)

没有字典时 find 为 undefined,后面的 if (find) 本来就能兜住。


问题 5:修完逻辑后多出来的 unused

去掉拼接后,updateFrequencyUnitList 字典不再使用,eslint 报 assigned but never used。删掉未用的 useDict 即可,避免「为了展示单位去拉字典、实际却拼原始码」。


对照表

现象 类型原因 做法
A + B 报未定义 `T undefined不能直接+`
getIsCover 参数不匹配 接口 0/1 与 '0'/'1' 混用 参数写成 `number
没有 resourceNames 展示字段和类型声明不一致 in + Array.isArray,或改类型/改字段
.value.find 报未定义 Ref 初始可能是 undefined ?.find
unused 字典 重构后引用消失 删掉

可复用的原则

  1. 可选字段先当「可能没有」 ,再决定展示;不要靠 JS 的 undefined + ''。
  2. 函数入参跟接口走 ,不要想当然全是 number。
  3. 解构前确认键在类型里;运行时多出来的字段要用收窄,不要假装类型有它。
  4. 异步字典、表单实例一律 ?.。
  5. 不要用 as / ! 糊过去 ;这次是展示层兜底,所以用 ?? '' 和 || '-' 就够。

一句话:严格 TypeScript 逼你把「后端可能不给、字典可能没到、0 和 '1' 可能混用」写进代码。修的不是报错文案,是把展示逻辑和接口契约对齐。

相关推荐
康小旺18 小时前
TypeScript 编译器开始卷 Rust 了:Cloudflare 的 tsrs 到底想干嘛?
rust·typescript
nyaomaru1 天前
is-kit 达成 100 Stars ⭐🎉 从 50 到 100,发生了什么?
前端·typescript
ZoeLandia2 天前
React6:组件通信
前端·react.js·typescript
孟陬2 天前
解决动态 import 类型 Any 痼癖
typescript·node.js·bun
ZoeLandia2 天前
React03:useState 与不可变更新
前端·react.js·typescript·react
console.log('npc')2 天前
从0到1实现任务评价统计功能:React + TypeScript 实战指南(小白注释版)
前端·react.js·typescript
ZoeLandia2 天前
React04:事件处理与受控表单
开发语言·前端·react.js·typescript
ZoeLandia2 天前
React05:useEffect 与副作用
开发语言·前端·react.js·typescript
Flynt3 天前
Cloudflare 用 Rust 重写了 TypeScript:我拿同一批代码跑了跑,报错一模一样,退出码不一样
rust·typescript·前端工程化
落花看风雪3 天前
从零开始搭建后台系统(vue3+TypeScript+Vite)01
typescript·vue·js