Code前端首页关于Code前端联系我们

Vue2 里怎么用 optional chaining?要注意啥?

terry 13小时前 阅读数 13 #Vue
文章标签 Vue2;可选链

做Vue2项目时,有没有遇到过“Cannot read properties of undefined (reading 'xxx')”这种报错?尤其是处理接口返回的嵌套数据、组件传参里的可选属性时,稍不注意就踩坑,这时候optional chaining(可选链操作符)就能帮大忙!但Vue2里怎么用它?用的时候得避开哪些坑?今天咱们把这些问题拆开来聊透。

先搞懂什么是 optional chaining?

Optional chaining 是 JavaScript 的一个语法特性,长得像 ,作用是安全访问嵌套对象的属性或方法,举个简单例子:

假如有个用户对象 const user = { info: { name: '小明' } }; ,要拿 name ,正常写 user.info.name 没问题,但如果接口返回数据不完整,user.info 变成 undefined 了,直接写 user.info.name 就会报错 “Cannot read properties of undefined”。

用可选链的话,写成 user?.info?.name ,JS 引擎会这么处理:先看 user 是不是 null/undefined ,如果是,直接返回 undefined ,不继续往下访问;如果不是,再看 user.info 是不是 null/undefined ,同理,最后才访问 name ,这样就不会因为中间某一层不存在而报错,相当于自动帮你做了“存在性检查”。

除了访问属性,可选链还能用来调用函数或者访问数组索引:

  • 函数调用: func?.() —— func 是函数,就执行;如果是 null/undefined ,不执行也不报错,返回 undefined
  • 数组索引: list?.[0]?.name —— 先检查 list 是否存在,再检查第 0 项是否存在,最后取 name

Vue2 项目里怎么用 optional chaining?

Optional chaining 是 ES2020 才引入的语法,浏览器和 Node.js 早期版本并不支持,Vue2 项目要用上它,得靠构建工具(Webpack + Babel)把新语法转成旧版本能识别的代码,具体分两步:

安装 Babel 插件

需要安装 @babel/plugin-proposal-optional-chaining 这个插件,它能把 语法转译成兼容旧浏览器的代码,在项目根目录执行命令:

npm install --save-dev @babel/plugin-proposal-optional-chaining

(用 yarn 的话就把 npm 换成 yarn 就行。)

配置 Babel

打开项目里的 babel.config.js (或 .babelrc ),在 plugins 数组里加上这个插件:

module.exports = {
  plugins: [
    '@babel/plugin-proposal-optional-chaining',
    // 其他插件...
  ]
};

配置完后,重新启动项目(npm run serve ),Babel 就会自动处理 语法了。

在 Vue2 模板和 JS 里用起来

配置好后,不管是 模板(.vue 文件的 <template> 里) 还是 JS 代码(<script> 里的 methods、computed 等) ,都能放心用可选链:

  • 模板里渲染数据: <p>{{ user?.info?.name }}</p> ,就算 useruser.infonull/undefined ,页面也不会报错,只会显示空内容;
  • JS 里处理逻辑:比如在 methods 里写 handleData() { const nick = this.user?.info?.nickname; } ,避免因为数据缺失导致程序崩溃。

为啥在 Vue2 里推荐用 optional chaining?

在 Vue2 项目里,处理嵌套数据是家常便饭——比如接口返回的用户信息里嵌套了地址、订单列表里嵌套了商品详情,要是不用可选链,传统写法会很“啰嗦”,还容易出错,咱们对比下几种常见写法:

传统写法:多层 && 判断

为了避免报错,过去常这么写:

const name = user && user.info && user.info.name;

如果嵌套层级更深,user.info.address.city ,就得写成 user && user.info && user.info.address && user.info.address.city ,代码又长又丑,可读性差。

传统写法:三元表达式

有人用三元表达式简化,但嵌套多了更乱:

const name = user ? (user.info ? user.info.name : undefined) : undefined;

三层嵌套就已经很绕了,要是五层、六层,根本没法维护。

传统写法:try...catch

还有人用 try...catch 包裹,但这种写法在模板里根本没法用(Vue 模板不支持直接写 try...catch ),而且在 JS 里用也会让代码变冗余:

let name;
try {
  name = user.info.name;
} catch (e) {
  name = undefined;
}

显然,这种写法只适合极少数复杂场景,大部分时候没必要。

可选链的优势:简洁、安全、可读性高

用 之后,上面的例子可以简化成 user?.info?.name ,一行代码搞定多层嵌套的安全访问,不管是模板里渲染,还是 JS 里处理逻辑,代码量少了一大半,读起来也更顺畅。

Vue2 项目里经常遇到异步数据加载的情况——比如页面刚加载时,用户数据还没从接口拿回来(userundefined ),这时候用可选链能避免页面渲染时直接报错,等数据回来后再正常显示,体验更友好。

Vue2 中 optional chaining 的实际场景案例

光说理论太虚,结合实际开发场景看看可选链怎么解决问题:

场景 1:列表渲染时处理嵌套数据

假设接口返回的订单列表结构是这样的:

const orders = [
  { 
    id: 1, 
    product: { name: '手机', price: 3999 }, 
    status: '已支付' 
  },
  { 
    id: 2, 
    product: null, // 某个订单没有商品信息
    status: '已取消' 
  }
];

在 Vue2 模板里渲染订单列表,要显示商品名称:

<ul>
  <li v-for="order in orders" :key="order.id">
    商品:{{ order?.product?.name }} —— 状态:{{ order.status }}
  </li>
</ul>

如果不用可选链,第二个订单的 order.product.name 会直接报错,用了 order?.product?.name 后,当 productnullundefined 时,这部分会显示空,页面正常渲染。

场景 2:事件处理中访问事件对象属性

在 Vue2 里给元素绑定点击事件,要获取事件目标的 dataset 数据:

<button @click="handleClick" data-id="123">点我</button>

如果直接写 event.target.dataset.id ,当事件目标不是预期元素(比如事件冒泡到父元素),event.target 可能不是按钮,就会报错,用可选链处理:

methods: {
  handleClick(event) {
    const id = event?.target?.dataset?.id;
    if (id) {
      // 处理 id
    }
  }
}

这样即使 event.targetnull 或者没有 dataset ,也不会报错,代码更健壮。

场景 3:组件传参时处理可选属性

父组件给子组件传参,某些属性可能是可选的,比如父组件传 userInfo ,子组件接收后要访问嵌套属性:

<!-- 父组件 -->
<ChildComponent :user-info="userInfo" />
<!-- 子组件 -->
<script>
export default {
  props: {
    userInfo: {
      type: Object,
      default: () => ({})
    }
  },
  computed: {
    nickName() {
      // 父组件可能没传 userInfo,或者 userInfo.nick 不存在
      return this.userInfo?.nick;
    }
  }
}
</script>

子组件里用 this.userInfo?.nick ,不管父组件传的 userInfo 是否完整,都能安全拿到 nick ,避免渲染或计算属性报错。

场景 4:异步请求后处理响应

用 axios 发请求获取数据时,响应结构可能不确定:

methods: {
  async fetchData() {
    try {
      const res = await axios.get('/api/user');
      // 假设接口可能返回 { data: { list: [...] } } 或者 { data: null }
      const list = res?.data?.list || [];
      this.tableData = list;
    } catch (error) {
      console.error(error);
    }
  }
}

如果接口返回的 res.datanull ,直接写 res.data.list 会报错,用可选链 res?.data?.list 就安全多了,再配合 || [] 给个默认值,页面渲染列表时更稳定。

使用 optional chaining 要注意的坑

可选链虽然好用,但不是万能的,这些细节没注意就容易踩坑:

不能用来赋值

可选链是访问操作符,不是赋值操作符,比如想给嵌套属性赋值:

// 错误写法!会报错:Invalid left-hand side in assignment
user?.info?.name = '新名字';

如果要给可能不存在的属性赋值,得先判断存在性:

if (user && user.info) {
  user.info.name = '新名字';
}

和空值合并操作符(??)结合时注意优先级

空值合并操作符 用来给 null/undefined 设默认值,和可选链结合很常用:

const name = user?.info?.name ?? '匿名';

这里逻辑是:user?.info?.namenull/undefined ,就取 '匿名' ,但要注意运算符优先级——如果表达式更复杂,必要时加括号明确执行顺序。

// 想表达:(user?.info?.name) ?? ('默认' + '后缀')
const name = user?.info?.name ?? '默认' + '后缀'; 
// 实际执行顺序是 user?.info?.name ?? ('默认' + '后缀'),和预期一致,所以简单场景不用括号;
// 但如果是 user?.info?.name ?? (getDefaultName() + '后缀'),最好保留括号增加可读性。

函数调用时要确保“可能是函数”

func?.() 时,func 得是函数或者 null/undefinedfunc 是其他类型(比如字符串、数字),调用时还是会报错:

const func = '不是函数';
func?.(); // 报错:func is not a function

所以只有确定 func 可能是函数或空值时,才适合用可选链调用。

数组访问的注意点

可选链支持数组索引访问,list?.[0]?.name ,但要注意 list 得是数组或者 null/undefinedlist 是其他对象(比如普通对象),list?.[0] 会取 list[0] 的值(对象的数字键),这可能不是你想要的,所以得确保 list 是数组类型再这么用。

构建工具配置要到位

Babel 插件没装或者没配置,Vue2 项目里写 会直接报错,所以一定要检查 package.json 里有没有 @babel/plugin-proposal-optional-chaining ,以及 babel.config.js 里的插件配置是否正确。

Vue2 模板的“隐性限制”

Vue2 的模板语法是基于自身的解析器,虽然大部分情况下 能正常转译,但如果项目里用了特别老的 Vue 版本(2.5 及以下),可能存在兼容性问题,建议把 Vue2 升级到 2.6+ 版本,对新语法的支持更友好。

和 Vue3 中 optional chaining 的区别?

很多同学同时接触 Vue2 和 Vue3,会好奇两者在可选链支持上的区别,简单说,主要是语法支持的“基础设施”不同

  • Vue3 项目如果用 Vite 构建,Vite 基于 ESBuild,默认支持大部分 ESNext 特性(包括可选链),不需要额外配置 Babel 插件;如果用 Webpack,也得配 Babel 插件,但 Vue3 本身对新语法的兼容性更好。
  • Vue2 因为诞生更早,对 ES2020+ 语法的支持完全依赖构建工具(Babel + Webpack),所以必须手动配置插件才能用可选链。

可选链是 JavaScript 语法特性,和 Vue 的响应式系统关系不大,不管是 Vue2 还是 Vue3,只要构建工具处理了语法,使用方式和效果是一致的——都是为了安全访问嵌套数据。

Vue2 里用 optional chaining 能大幅简化嵌套数据的访问逻辑,减少报错概率,但得注意配置构建工具、避免赋值等错误用法,把这些知识点吃透,处理复杂数据结构时就能更顺手,代码也更简洁易维护~ 要是你在实际项目里还有其他疑问,评论区随时交流~

版权声明

本文仅代表作者观点,不代表Code前端网立场。
本文系作者Code前端网发表,如需转载,请注明页面地址。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

热门