[Vuejs]-Two-way computed property (get,set) with 20+ properties

0👍

Try this:

let computed;
let list = ["exp", "fund"];

this.$emit = (s, value) => {
    let key = Object.keys(value)[0];
    this.job[key] = value[key];
}

this.job = {
    exp: 20
}

list.map((v) => {
    computed = {
        ...computed,
        [v]: {
            get: () => {
                return this.job[v];
            },
            set: (value) => {
                return this.$emit('job', { [v]: value });
            }
        }
    }
})

console.log("get", computed.exp.get());
computed.exp.set(10);
console.log("get", computed.exp.get());

0👍

You can use the $watch api with the deep: true option. Here’s a basic implementation with an increment function:

new Vue({
  el: "#app",
  data: {
    job: {
      a: 11,
      b: 22,
      c: 33,
      d: 44,
      e: 55,
      f: 66,
      g: 77,
      h: 88,
      // ...
    },
  },
  created() {
    // use $watch interface with the deep option
    this.$watch('job', (newVal, oldVal) => {
      // check for changes from the previous state
      Object.keys(newVal).forEach(key => {
        if(newVal[key] !== oldVal[key]) {
          console.log('changed job attr', key, newVal[key])
          this.$emit('job', {[key]: newVal[key]})
        }
      })
    }, {deep: true})
  },
  methods: {
    increment(key) {
      // needs to be reassigned rather than mutated if you want
      // the $watch callback to get the correct oldVal
      const val = this.job[key] + 1
      this.job = Object.assign({}, this.job, {[key]: val})
    }
  },
})

html to demo the increment function:

<div id="app">
  <ul>
    <li v-for="(val, key) in job" :key="key" @click="increment(key)">
      {{ key }}: {{ val }}
    </li>
  </ul>
</div>

0👍

I assume you’re using the get/set approach so that you can use v-model. Personally I would probably ditch the v-model and use the separate prop/event pair.

However, that isn’t to say that generating repeated computed properties cannot be done:

const properties = ['exp', 'fund']
const computed = {}

for (const property of properties) {
  computed[property] = {
    get () {
      return this.job[property]
    },
    
    set (value) {
      this.$emit('job', { [property]: value })
    }
  }
}

const Child = {
  template: `
    <div>
      <input v-model="exp">
      <input v-model="fund">
    </div>
  `,
  
  props: ['job'],
  
  computed: {
    ...computed
    // other computed properties here
  }  

}

new Vue({
  el: '#app',
  
  components: {
    Child
  },
  
  data () {
    return {
      job: {
        exp: 'hello',
        fund: 'goodbye'
      }
    }
  },
  
  methods: {
    updateJob (changes) {
      for (const property in changes) {
        this.$set(this.job, property, changes[property])
      }
    }
  }
})
<script src="https://unpkg.com/vue@2.6.10/dist/vue.js"></script>

<div id="app">
  <Child :job="job" @job="updateJob"></Child>
  <pre>{{ job }}</pre>
</div>

Each computed property just needs an object with a get and set method. It’s important to realise that these are not get and set in the defineProperty sense. Instead they are just normal methods that happen to be called get and set.

We need one such object for each computed property, so we create them in a loop and add them all to an object (which I’ve called computed) that holds all of these computed properties.

Then, in the component definition, these properties are spread using the ... operator. Strictly speaking the spreading isn’t necessary unless there are other computed properties that aren’t present in that object.

You could move all of the code for building the computed properties inline under the computed property, rather than having it at the start. There are two ways to do that, either by using reduce or by wrapping it all in an IIFE than returns the relevant object. To my mind that would look a bit unwieldy so I’ve kept it outside instead.

Leave a comment