vite 将react老项目中的没有兼容处理的写法转成兼容性写法

背景与目标

背景 :后端接口技改后,查询无数据时将返回 null 而非空对象,这可能导致前端老代码因未做兼容处理而出现运行时错误(如访问 null 的属性、对 null 进行解构等)。

目标 :针对指定文件 / 文件夹内的 JavaScript/TypeScript 代码(js/jsx/ts/tsx),自动添加可选链(?.)和逻辑表达式(||/&&),确保对对象、数组的操作在遇到 null/undefined 时不报错,兼容后端返回格式的变化。

使用和安装

npx -y @chagee/fix-optional babel-fix ./src 可以指定文件或者文件夹

技术方案

采用 Babel 插件转换 方案,通过 AST(抽象语法树)遍历代码,对关键节点进行自动修改,核心流程如下:

  1. 基于 Babel 插件机制:通过自定义 Babel 插件,在代码转译阶段拦截并处理 AST 节点。
  2. 节点钩子处理:针对可能引发错误的语法节点(成员访问、解构、赋值等),编写访问器钩子(Visitor),自动添加容错逻辑。
  3. TypeScript 兼容:处理 TypeScript 代码时,先剥离泛型等类型信息,确保转换逻辑专注于运行时代码。

术语解释优化

  1. LogicalExpression(逻辑表达式) 指包含逻辑运算符 ||(逻辑或)或 &&(逻辑与)的表达式,用于组合或判断多个条件。示例:a || [](若 a 为假值则返回空数组)、a && b(若 a 为真值则返回 b)。核心结构包含:

    1. left:左侧表达式
    2. right:右侧表达式
    3. operator:逻辑运算符(||&&
  2. MemberExpression | OptionalMemberExpression(成员访问表达式 | 可选成员访问表达式) 用于访问对象成员的表达式,包含以下关键属性:

    1. Optional:标记是否为可选访问(即是否带 ?. 运算符)。
    2. Computed:标记是否为计算属性访问(如 a[b][c][b][c] 为计算属性)。
    3. Object:当前成员的父级对象(如 a.b.c 中,a.bcObjectabObject)。
    4. Property:当前访问的属性(如 a.b.c 中,c 是最终的 Property)。
  3. VariableDeclarator(变量声明器) 用于变量声明与赋值的表达式,核心属性:

    1. id:赋值语句的左侧(被赋值的变量或解构模式)。
    2. init:赋值语句的右侧(赋值的初始值)。
  4. SpreadElement(扩展元素) 指使用 ... 语法的解构元素,用于将数组或对象的属性展开(如 [a, ...b]{x, ...y})。

  5. UpdateExpression(更新表达式) 包含自增(++)或自减(--)运算符的表达式,用于修改变量的值(如 a++obj.prop--)。

  6. AssignmentExpression(赋值表达式) 用于变量或属性赋值的表达式(区别于 VariableDeclarator,后者是声明时的赋值),如 a.b = 1x = y + z

  7. CallExpression | OptionalCallExpression(函数调用表达式 | 可选函数调用表达式) 用于调用函数或方法的表达式,Optional 标记是否为可选调用(即是否带 ?.() 语法),如 fn()obj.method?.()

核心实现逻辑补充与优化

MemberExpression | OptionalMemberExpression(成员访问表达式)

问题 :深层属性访问可能因中间值为 undefinednull 报错(如 a = {}; a.b.c 会报错)。优化目标 :自动为深层成员访问添加可选链(?.),避免报错。

实现逻辑

  • 递归遍历成员表达式的层级(如 a.b.c 拆解为 aa.ba.b.c)。
  • 对每个非顶层的成员访问添加 ?. 运算符(顶层不添加,避免 ?.a 这类无效语法)。
  • 对计算属性(如 a[b])转换为 a?.[b]

示例

css 复制代码
a.b.c        →  a?.b?.c  
a[b][c]      →  a?.[b]?.[c]  

VariableDeclarator(变量声明器)

问题 :数组或对象解构时,若右侧初始值为 undefinednull,会导致解构失败(如 const [x] = undefined 报错)。优化目标:为解构的初始值添加默认兜底,确保解构安全。

实现逻辑

  • 判断左侧 id 是数组解构([x, y])还是对象解构({x, y})。
  • 若右侧 init 不是 LogicalExpression,则为其包裹 || []|| {}(根据解构类型)。

示例

css 复制代码
// 数组解构const [a, b] = xxx  →  const [a, b] = xxx || []  

// 对象解构const {a, b} = xxx  →  const {a, b} = xxx || {}  

SpreadElement(扩展元素)

问题 :扩展运算符后的变量若为 undefinednull,会导致解构报错(如 [...undefined] 报错)。优化目标:为扩展元素添加默认兜底,确保其为有效数组或对象。

实现逻辑

  • 根据父级上下文判断是数组扩展(如 [...b])还是对象扩展(如 {...b})。
  • 若扩展的变量(argument)不是 LogicalExpression,则包裹为 ...(b || [])...(b || {})

示例

css 复制代码
// 数组扩展[a, ...b]      →  [a, ...(b || [])]  

// 对象扩展{a, ...b}      →  {a, ...(b || {})}  

UpdateExpression(更新表达式)

问题 :自增 / 自减操作的目标若为 undefinednull,会报错(如 a.b.c++a.bnull 时)。优化目标:确保更新操作仅在目标有效时执行,避免报错。

实现逻辑

  • 提取更新表达式的目标(如 a.b.c++ 中的 a.b.c)。
  • 构建逻辑表达式:目标.toString() && 原更新表达式(利用 toString() 验证目标是否有效)。
  • 标记右侧更新表达式为 "已处理",避免被其他钩子重复修改。
  • 左侧表达式会被 MemberExpression 钩子处理,自动添加可选链。

示例

css 复制代码
a.b.c++  →  a?.b?.c?.toString() && a.b.c++  

AssignmentExpression(赋值表达式)

问题 :对不存在的深层属性赋值会报错(如 a.b.c = 1a.bundefined 时)。优化目标:确保赋值操作仅在父级属性有效时执行。

实现逻辑

  • 提取赋值左侧的表达式(如 a.b.c = 1 中的 a.b.c),并移除最后一层属性(得到 a.b)。
  • 构建逻辑表达式:父级表达式 && 原赋值表达式(确保父级存在时才执行赋值)。
  • 标记右侧赋值表达式为 "已处理",避免重复修改。
  • 左侧父级表达式会被 MemberExpression 钩子处理,自动添加可选链。

示例

css 复制代码
a.b.c = 1  →  a?.b && a.b.c = 1  

CallExpression | OptionalCallExpression(函数调用表达式)

问题 :调用 undefinednull 的方法(如数组方法)会报错;部分方法(如 concat)的参数若为非数组可能导致异常。优化目标:确保方法调用的主体和参数有效。

实现逻辑

  • 方法主体处理 :若调用的是数组方法(如 mapfilterconcat),为主体添加 || [] 兜底(如 a.b.filter()(a.b || []).filter())。
  • 参数处理 :对 concat 等需要数组参数的方法,为其第一个参数添加 || [] 兜底(如 a.b.concat(c)(a.b || []).concat(c || []))。
  • 可选调用转换 :将普通调用转换为可选调用(如 a.b()a.b?.()),避免主体为 null/undefined 时的报错。

示例

scss 复制代码
a.b.filter(x => x>0)  →  (a.b || []).filter(x => x>0)  
a.b.concat(c)         →  (a.b || []).concat(c || [])  
obj.method()          →  obj.method?.()  

核心实现图

代码仓库

github

参考文档

Babel 插件手册

相关推荐
GISBox5 小时前
GISBox如何让GeoTIFF突破Imagery Provider加载限制?
react.js·json·gis
天蓝色的鱼鱼6 小时前
Next.js 渲染模式全解析:如何正确选择客户端与服务端渲染
前端·react.js·next.js
果粒chenl9 小时前
React学习(四) --- Redux
javascript·学习·react.js
LRH11 小时前
React 双缓存架构与 diff 算法优化
前端·react.js
中微子12 小时前
别再被闭包坑了!React 19.2 官方新方案 useEffectEvent,不懂你就 OUT!
前端·javascript·react.js
1in12 小时前
一文解析UseState的的执行流程
前端·javascript·react.js
鹏多多12 小时前
React无限滚动插件react-infinite-scroll-component的配置+优化+避坑指南
前端·javascript·react.js
阿喵派我来抓鱼16 小时前
深入理解 AI 流式接口:从请求到响应的完整解析
react.js·ai·前端框架·vue
DoraBigHead1 天前
React 架构重生记:从递归地狱到时间切片
前端·javascript·react.js