首页 > 解决方案 > Vue v-model and proxy first set is not called

问题描述

Currently I am working on a Vue project where I want to have a model class that stores all its initial data in two properties: original and attributes. However I am having difficulties with binding properties to an input via v-model.

Lets say I have the following class:

class Model
{
    constructor () {
        return new Proxy(this, {
            get: (target, property, receiver) => {
                console.log('get');
                return target[property];
            },

            set: (target, property, value, receiver) => {
                console.log('set');
                target[property] = value + 123;
                return true;
            }
        });
    }
}

This class will log get when getting a property and set when setting a property. Also while setting it will add 123 at the end of the value. The following will be executed:

let model = new Model();

model.name = 'test'; // Logs: 'set'

console.log(model.name); // Logs: 'get' and 'test123'

This works as expected.

The problems start when I bind these properties to an input via v-model. The following happens:

Actions are executed on:

<input v-model="model.name" />
...
data () {
    return {
        model: new Model
    };
}

On the first change the property name will be added to the model object. No log at all. No added 123 at the end of the value. The model value has changed to { model: 't' }.

Second change it will log set and it will add 123. The model value has changed to { model: 'te123' }.

Logging the arguments of of the set call during the second change: Model {__ob__: Observer}, "name", "te", Proxy {__ob__: Observer}

Anyone had this problem before or knows what is going on here?

After answer of @sphinx

Thank you for your answer! It works!

To make things more complicated I want to have the properties that are in a certain fillable array to be set in a property called attributes. However the same behaviour returns.

class Model
{
    constructor () {
        this.attributes = {};
        this.fillable = ['name'];

        return new Proxy(this, {
            get: (target, property, receiver) => {
                console.log('get', property);

                if (target.fillable.indexOf(property) > -1) { // Check if in fillable
                    if (!(property in  target.attributes)) {
                        Vue.set(target.attributes, property, null);
                    }

                    return target.attributes[property];
                }

                return target[property];
            },
            set: (target, property, value, receiver) => {
                console.log('set', property, value);

                if (target.fillable.indexOf(property) > -1) {
                    Vue.set(target.attributes, property, value + 123);
                } else {
                    target[property] = value
                }

                return true;
            }
        });
    }
}
Vue.config.productionTip = false

new Vue({
  el: '#app',
  data() {
    return {
      test: new Model
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

<div id="app">
  <span>{{test.name}}</span>
    <input v-model="test.name" type="text">
</div>

During the first change the name property is set directly on the model object. But the set method is not called.

During the second change the name property is changing and the set method is called.

Any suggestions on why this happens?

标签: javascriptvue.jsproxy

解决方案


在您的示例中,getter 将创建一个属性,但它不是反应性的(查看Vue 指南:深度反应性)。

因此 Vue 无法跟踪更改

该解决方案很简单,如上述指南介绍、使用Vue.setvm.$set确保您的对象具有反应性。

就像下面的演示:

class Model
{
    constructor () {
        return new Proxy(this, {
            get: (target, property, receiver) => {
                console.log('get', property);
                if(!(property in target)) Vue.set(target, property, null)
                return target[property]
            },
            set: (target, property, value, receiver) => {
                console.log('set', property, value);
                Vue.set(target, property, value + 123)
                return true;
            }
        });
    }
}
Vue.config.productionTip = false
new Vue({
  el: '#app',
  data() {
    return {
      test: new Model
    }
  }
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.16/vue.js"></script>

<div id="app">
  <span>{{test.name}}</span>
    <input v-model="test.name" type="text">
</div>


推荐阅读