TypeScript中如何优雅处理ant-design-vue的a-select的默认空值

如果你也使用ant-design-vue和TypeScript,你可能遇到过和我一样的问题:在TypeScript环境下使用ant-design-vue的a-select组件时,如何优雅地处理默认空值。听起来好像很复杂,实际一点也不简单。

空值三兄弟:""nullundefined

当我们想让a-select默认显示placeholder时,通常会想到这三个"falsy"值:空字符串""nullundefined

vue 复制代码
<template>
  <a-select v-model:value="fruit" placeholder="请选择你的水果" />
</template>

<script lang="ts" setup>
import { ref } from 'vue'
const fruit = ref() // ? ref('')? ref(null)? ref(undefined)?
</script>

那就逐一尝试一下。

空字符串""

当你赋予空字符串,会发现本该在空值时显示的placeholder并没有显示,这是因为ant-design-vue认为空字符串是一个有意义的值,而非空值(这一点后文的issue截图中可以印证)。

null

null怎么样呢,尝试发现placeholder显示正常,但编辑器会触发TypeScript报错提示:不能将类型"null"分配给类型"SelectValue"。(当然,如果你不使用TypeScript,就没有这个问题,直接使用null没有任何问题)。

html 复制代码
<template>
  <a-select v-model:value="fruit" />
  <!--              ^? 不能将类型"null"分配给类型"SelectValue"。 -->
</template>

我们来看下SelectValue的类型定义:

ts 复制代码
export declare type SelectValue = RawValue | RawValue[] | LabeledValue | LabeledValue[] | undefined;
ts 复制代码
declare type RawValue = string | number;
export interface LabeledValue {
    key?: string;
    value: RawValue;
    label?: any;
}

undefined

从类型定义可以看到,官方应该是希望我们使用undefined作为默认空值。使用undefined也确实不影响placeholder的显示,但会有另一个更致命的问题:值为undefined的字段在fetch/XHR请求中很可能会被过滤掉

我使用axios作为请求库,当我使用content-type: application/json发送带有值为undefined的字段时,该字段会被过滤掉而不会出现在最终的Request Payload中。这是因为axios在源码中会使用JSON.stringify()来序列化请求参数。

这部分源码在lib/defaults/index.js

js 复制代码
if (isObjectPayload || hasJSONContentType ) {
  headers.setContentType('application/json', false);
  return stringifySafely(data);
}

以及stringifySafely的实现

js 复制代码
function stringifySafely(rawValue, parser, encoder) {
  if (utils.isString(rawValue)) {
    try {
      (parser || JSON.parse)(rawValue);
      return utils.trim(rawValue);
    } catch (e) {
      if (e.name !== 'SyntaxError') {
        throw e;
      }
    }
  }

  return (encoder || JSON.stringify)(rawValue);
}

undefinedJSON.strigify()的序列化过程中会被忽略,下面是引用的MDN的原文,可以看到更多细节:

undefined、任意的函数以及 symbol 值,在序列化过程中会被忽略(出现在非数组对象的属性值中时)或者被转换成 null(出现在数组中时)。函数、undefined 被单独转换时,会返回 undefined,如JSON.stringify(function(){}) or JSON.stringify(undefined).

当然这不仅仅是axios的问题,如果你使用fetch APIcontent-type: application/json的请求可能也需要使用JSON.stringfy来处理,MDN的示例代码里就是这么做的。其它的请求库我没有求证,大概率也有这个问题。

社区的声音

null运行时表现正常,但类型不匹配。undefined类型匹配,但运行时不特殊处理会出bug的啊。ant-design-vue的官方为什么就不能把null纳入SelectValue的类型联合中呢,这好像是最好的解决方案。其实不光是我这么想,早在2019年社区就有人提了这个issue

也有人建议官方把null纳入空值,但官方并没有做出回应,该issue已被超时自动关闭。并且截至目前为止的最新版本4.2.6该问题依旧存在。

解决方案

那能怎么办呢,官方不解决,我的业务代码还得写啊。我想到的解决方案有两种:

方案1

第一种方案是修改node_modules包中SelectValue的类型定义,并使用patch-package来打补丁。但为这么小的一类型问题打一个补丁,感觉是杀鸡用牛刀了。

方案2

第二种方案是在发送请求的时候手动把所有值为undefined的字段替换为null。既然是全局的,肯定不能每个请求的地方都执行一遍这个逻辑,axios的请求配置transformRequest允许在向服务器发送前,修改请求数据,这是最合适的修改方式。

我们先来实现这个数据转换的核心函数,需要考虑到对象深层的递归处理:

ts 复制代码
/**
 * 递归替换对象中所有的 undefined 为 null
 * @param obj
 * @returns
 */
function replaceUndefinedWithNull(obj: Record<string, any>): Record<string, any> {
  if (typeof obj !== 'object' || obj === null) {
    return obj // 如果不是对象或数组,直接返回
  }

  // 处理数组
  if (Array.isArray(obj)) {
    return obj.map((item) => replaceUndefinedWithNull(item))
  }

  // 处理普通对象
  const result: Record<string, any> = {}
  for (const key in obj) {
    if (obj.hasOwnProperty(key)) {
      const value = obj[key]
      if (value === undefined) {
        result[key] = null // 替换 undefined 为 null
      } else {
        result[key] = replaceUndefinedWithNull(value) // 递归处理嵌套对象或数组
      }
    }
  }
  return result
}

然后是配置,需要注意,我们直接给transformRequest赋新值会覆盖掉axios内置的transformRequest,我们只需要把内置的默认transformRequest取出来再塞进去即可:

ts 复制代码
axios.create({
  transformRequest: [
    replaceUndefinedWithNull,
    (axios.defaults.transformRequest as AxiosTransformer[])[0],
  ],
  // other configs
})

总结

本来是个小问题,但还是展开说了不少。就连五星评论家麦克阿瑟也忍不住总结道:

  • 在TypeScript中使用ant-design-vue的a-select组件时,如何处理默认空值是个问题:空字符串不能正常显示placeholder;null会导致TypeScript类型报错;undefined很可能会导致在请求发起时被从最终的请求参数中过滤掉该字段。这是因为JSON.stringify()序列化会忽略掉undefined
  • 如果不使用TypeScript,直接使用null是最简单的方案。
  • 社区有声音呼吁官方把null纳入空值处理,但并没有得到回应和处理。
  • 比较好的解决方案是在发起请求前统一处理请求参数,比如axios可以在transformRequest中进行处理。
相关推荐
岁月宁静14 小时前
大规模图片列表性能优化:基于 IntersectionObserver 的懒加载与滚动加载方案
前端·javascript·vue.js
一 乐14 小时前
医疗保健|医疗养老|基于Java+vue的医疗保健系统(源码+数据库+文档)
java·前端·数据库·vue.js·毕设
Sheldon一蓑烟雨任平生1 天前
Vue3 插件(可选独立模块复用)
vue.js·vue3·插件·vue3 插件·可选独立模块·插件使用方式·插件中的依赖注入
鱼与宇1 天前
苍穹外卖-VUE
前端·javascript·vue.js
裴嘉靖1 天前
Vue 生成 PDF 完整教程
前端·vue.js·pdf
毕设小屋vx ylw2824261 天前
Java开发、Java Web应用、前端技术及Vue项目
java·前端·vue.js
冴羽1 天前
今日苹果 App Store 前端源码泄露,赶紧 fork 一份看看
前端·javascript·typescript
时间的情敌1 天前
Vite 大型项目优化方案
vue.js
西洼工作室1 天前
高效管理搜索历史:Vue持久化实践
前端·javascript·vue.js
Jeffrey__Lin1 天前
解决Grid布局下el-table自适应缩小失败的问题
vue.js·elementui·html