在 Vue3 的项目中使用了 JS 对象的 setter 和 getter 属性,打包后失效是怎么回事?

hooozen
  • 73

问题概述:

由于前端 Vue 组件需要绑定 v-model数据格式和接口返回和上传的格式不一样,为了方便使数据“双向同步”,我想到使用 JS 对象的 settergetter 属性来实现,事实证明这也是很方便的.

一切在我本地开发时都正常,最近打包后发布在测试环境后发现出了 Bug,好像是 settergetter 没有生效.

如果打包前正常,打包后出了 Bug 是不是打包这里出问题了呢,所以想问问有没有了解 Vue 和 Vite 打包机制的大佬,看能不能帮忙定位下问题.

开发环境:

使用了 Vue3 开发,并配套使用了 Vite.js 打包(被Vue主页各种忽悠使用的)
Vue 3.0.5
Vite 2.1.5
element-plus: ^1.0.2-beta.39

涉案代码:

组件使用了 ElementUI 的 time-pickerrange 模式,v-model 需要绑定 Date 数组.

<el-form-item prop="_timeRange" label="营业时间:">

    <el-time-picker is-range 
        v-model="localForm._timeRange" 
        range-separator="至" start-placeholder="开始时间"
        end-placeholder="结束时间" 
        placeholder="选择时间范围">
    </el-time-picker>
    
</el-form-item>

服务端返回和要求上传的数据格式中,这两个时间是两个字符串字段

{
  "openingTimeEnd": "string",
  "openingTimeStart": "string"
}

于是我从服务端获取表单后,做了如下处理:

const localForm = {
    ...form,
    
    set _timeRange(value) {
      console.log(value)
      this.openingTimeStart = moment(value[0]).format('YYYY-MM-DD HH:mm:ss')
      this.openingTimeEnd = moment(value[1]).format('YYYY-MM-DD HH:mm:ss')
    },

    get _timeRange() {
      this.openingTimeEnd = this.openingTimeEnd || "1949-10-01 17:00:00"
      this.openingTimeStart = this.openingTimeStart || "1949-10-01 09:00:00"
      return [new Date(this.openingTimeStart), new Date(this.openingTimeEnd)]
    },
}

这样一来,每当修改 _timeRange 的时候,都会自动设置 localForm 中的 openingTimeStartopeningTimeEnd 字段.

这一效果在我本地调试时也是正常的,但打包后就失效了,表现为我设置时间后,openingTimeStartopeningTimeEnd 字段并不更新

请教!

这个 Bug 我没有头绪,难道要放弃这种思路,然后去监听事件手动修改字段吗,请大佬们不吝赐教

补充

在我本地调试时,打印我处理后的表单如下:

可以看到我设置的多个 setget

打包后,打印的结果如下:

image.png

所有的 getset 消失了!

回复
阅读 138
1 个回答

更新

在 Github 上找到了该 issue#3247,具体的原因如下:

Bug 原因

为了定位 Bug,我想到去查看打包后,代码到底发生了什么变化。阅读打包后的代码,我发现问题出在打包后,对 ES6 解构语法的转译,即对以下代码的转译:

const localForm = {
    ...obj,
    
    property1: 'somevalue1',
    property2: 'somevalue2',
    
    set _value(value) {
        \\ some code
    },
    get _value() {
        \\ some code
    }
}

打包过程中,会把以上涉及 ... 解构符号的代码转化为一个函数,类似以下代码:(其中 obj1 就是被解构的对象,obj2 就是把剩下的属性包装成一个对象)

var mergeObj = (obj1, obj2) => {
  for (var key in obj2 || {}) {
    if (hasOwnProperty.call(obj2, key)) setValue(obj1, key, obj2[key])
  }

  if (getOwnPropertySymbols) console.log('hanld symbols property')

  return obj1
}

代码考虑了普通属性和 Symbols 属性,却没考虑 settergetter. 问题就出在这里,在 for in 循环中,虽然 _value 作为属性可以被拷贝,但无法拷贝他的 gettersetter,这就导致了,_value 被拷贝后的值只是一个从 obj2 中的 setter 获取的一个静态值. 所以 bug 就出现了.

解决办法

问题出现在打包时对 ... 解构符的转译,那么可以避免在使用 settergetter 的同时避免使用 ... 符号,可以避免该问题.

但仍然感觉这应该是 vitejs 打包时的一个 Bug,不知道 vite.js 底层使用了什么工具打包,还是自己新写的,总之应该被解决.

Demo

根据边城的建议,我做了一个 bug 复现的Demo,(需要打包后才能复现 Bug)

其实原理上面应该介绍的差不多了,Demo 的关键代码如下:

const getFormData = () => {
  setTimeout(() => {
    /* 该写法不会产生bug
    formData.value = {
      openingTimeStart: '1949-10-01 09:00:00',
      openingTimeEnd: '1949-10-01 18:00:00',

      set _timeRange(value: | string[] | Date[]) {
        this.openingTimeStart = moment(value[0]).format('YYYY-MM-DD HH:mm:ss')
        this.openingTimeEnd = moment(value[1]).format('YYYY-MM-DD HH:mm:ss')
      },

      get _timeRange() {
        this.openingTimeEnd = this.openingTimeEnd || "1949-10-01 17:00:00"
        this.openingTimeStart = this.openingTimeStart || "1949-10-01 09:00:00"
        return [new Date(this.openingTimeStart), new Date(this.openingTimeEnd)]
      },
    }
    */

    // 下面写法产生 Bug
    const res = {
      openingTimeStart: '1949-10-01 09:00:00',
      openingTimeEnd: '1949-10-01 18:00:00',
    }

    formData.value = {
      ...res,

      set _timeRange(value: | string[] | Date[]) {
        this.openingTimeStart = moment(value[0]).format('YYYY-MM-DD HH:mm:ss')
        this.openingTimeEnd = moment(value[1]).format('YYYY-MM-DD HH:mm:ss')
      },

      get _timeRange() {
        this.openingTimeEnd = this.openingTimeEnd || "1949-10-01 17:00:00"
        this.openingTimeStart = this.openingTimeStart || "1949-10-01 09:00:00"
        return [new Date(this.openingTimeStart), new Date(this.openingTimeEnd)]
      },
    }
  }, 100)
}
你知道吗?

宣传栏