vue watch 属性为对象时,一直触发监听事件的问题

1,200 阅读1分钟

父组件如下:

// parent.vue

<template>
  <a-form>
    <a-form-item>
      <a-input v-model="details.val1"></a-input>
    </a-form-item>
    <a-form-item>
      <a-select v-model="details.val2">
        <a-select-option
          v-for="item in dictionary"
          :value="item.code"
          :key="item.code"
        >{{ item.name }}</a-select-option>
      </a-select>
    </a-form-item>
    <a-form-item>
      <Child :value="{ code: details.code, name: details.name }"></Child>
    </a-form-item>
  </a-form>
</template>

<script>
import Child from './Child.vue'
export default {
  components: { Child },
  data() {
    return {
      dictionary: [],
      details: {}
    }
  },
  created() {
    setTimeout(() => {
      this.dictionary = [
        {
          code: 1,
          name: 'A'
        },
        {
          code: 2,
          name: 'B'
        }
      ]
    }, 500)
    setTimeout(() => {
      this.details = {
        val1: 'text test',
        val2: 1,
        code: '123',
        name: 'doorgod'
      }
    }, 1000)
  }
}
</script>

子组件:

// Child.vue

<script>
export default {
  props: {
    value: Object
  },
  watch: {
    value(val) {
      console.log(val)
      // do sth
    }
  }
}
</script>

这里存在一个问题,子组件内的监听函数会出现多次,dictionary以及details变更的时候都会触发,input以及select值变更的时候也会触发,跟预期的detail.code detail.name 变更触发相差甚远。

原因在于

{} != {} // true
{} !== {} // true

每当parent中数据变化重新渲染时,Child的value都是一个新对象,所以会触发监听。

解决方案:

// parent.vue

<template>
  <a-form>
    <a-form-item>
      <a-input v-model="details.val1"></a-input>
    </a-form-item>
    <a-form-item>
      <a-select v-model="details.val2">
        <a-select-option
          v-for="item in dictionary"
          :value="item.code"
          :key="item.code"
        >{{ item.name }}</a-select-option>
      </a-select>
    </a-form-item>
    <a-form-item>
      <Child :value="obj"></Child>
    </a-form-item>
  </a-form>
</template>

<script>
import Child from './Child.vue'
export default {
  components: { Child },
  data() {
    return {
      dictionary: [],
      details: {},
      obj: {}
    }
  },
  created() {
    setTimeout(() => {
      this.dictionary = [
        {
          code: 1,
          name: 'A'
        },
        {
          code: 2,
          name: 'B'
        }
      ]
    }, 500)
    setTimeout(() => {
      this.details = {
        val1: 'text test',
        val2: 1,
        code: '123',
        name: 'doorgod'
      }
      this.obj = {
        code: this.details.code,
        name: this.details.name
      }
    }, 1000)
  }
}
</script>

此时若details会更新,但是code,name不变也会触发,要完全符合预期需要:

this.obj.code = this.details.code
this.obj.name = this.details.name

且Child中监听value的属性

// Child.vue

watch: {
    'value.code'(val) {
      console.log(val)
    },
    'value.name'(val) {
      console.log(val)
    }
  }

然而代码量多了一堆。。。