简体   繁体   中英

Vue - listen props changes (object), watch not triggering

I have similiar problem to this: VueJs 2.0 - how to listen for `props` changes but in my case, I send object from parent to child, also with passing down few times to use benefits of object references in JS.

There is an example, but working fine:

<div id="app">
  <child :myprop="myObj"></child>
  <button @click="text = 'Another text'">Change text</button>
</div>

<script>
new Vue({
  el: '#app',
  data: {
    myObj: {
       id: null
    }
  },
  components: {
    'child' : {
      template: `<div>
      <p>{{ myprop.id }}</p>
      <select v-model="myprop.id">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      </div>`,
      props: ['myprop'],
      watch: { 
        'myprop.id': function(newVal, oldVal) { // watch it
          console.log('Prop changed: ', newVal, ' | was: ', oldVal)
        }
      }
    }
  }
});
</script>

https://jsfiddle.net/8hqwg6Ln/

In my case, I set v-model on select to "parentObj.id" and then add watcher for this. The goal is to change other parentObj property after changing id - user select id, after that I search this id in my array and set parentObj.price, which is v-model to other component (modified input) on the same level as select field.

When I change slection, watcher is not triggered. If I add @change with my custom method, it works, but with delay (I see this in vue dev tools, must refresh to get current data), also, it doesn't work for second input component - it doesn't see changes parentObj.price.

What is the best approach to use complex collection with passing down and modify this one collection with many different components? I think, I can add "copies" of properties to local level (so, create data with id and price on child), add watchers, and use this.$set to update received props. But... is it good option, maybe here is something better?

it' easy. you need add deep property to watch object.

props: ['myprop'],
  watch: { 
    myprop: {
        deep: true,
        handler: function(newVal, oldVal){
           console.log('Prop changed: ', newVal, ' | was: ', oldVal)
        }
      }
    }
  }

You don't need to use something like

'myprop.id'

try like that

 { ... props: ['myprop'], watch: { 'myprop.id': { handler(newVal, oldVal) { console.log('Prop changed: ', newVal, ' | was: ', oldVal) }, deep: true } ... }

There is only one problem in your code:

"text = 'Another text'"

doesn't work because text was not declared. So you might want to change it to

"myObj = { id: 'Another text' }"

I suppose this is what you want. Then it's reactive, the text changes to 1 or 2 or 3 when you select and to "Another text" when you click and your watcher fires and logs the old and the new value

First need a prop for parent component and a local data for select v-model:

<select v-model="localid">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>

and your child component's prop and data is:

data() {
      return {
        localid:null
      }
    },
props: ['pid']

if a component prop changed and he parent want to know this change you can use .sync modifier. First update your prop in your component. watch your localid data and then update your pid prop with your new data:

watch:{
     localid:function(){
     this.$emit('update:pid', this.localid)
    }
}

Now parent know that pid prop is changed and your parent component's data will change with prop's new data. Your parent component:

<div id="app">
  <child :pid="myObj.id"></child>
  <button @click="text = 'Another text'">Change text</button>
</div>

More info: https://v2.vuejs.org/v2/guide/components-custom-events.html#sync-Modifier

I tested some things and found bug and solution. First, I had a bug inside my custom input - it is my component, which use cleave-vue inside. Cleave-vue has v-model, I also used v-model on my custom component and it was wrong - I must use value binding with sync (for example: :value.sync="parentCost").

Second thing: yes, I must created values locally, then pass it to childs, and add watchers. In watchers, I make additional manipulations like parseInt/parseFloat etc and set parent data. Now everything works fine. It's some data duplicating, but I think not big issue - I create "abstract" mixin, because I have a lot of similar custom components, and every uses watchers/setters template. Code is clean.

You should be able to send the value of the child's prop 'myprop' to the parent app by simply using "$emit".

 new Vue({ el: '#app', data: { myObj: { id: null } }, components: { 'child' : { template: `<div> <p>{{ myprop.id }}</p> <select v-model="myprop.id" @change="$emit('update', myprop);"> <option value="1">1</option> <option value="2">2</option> <option value="3">3</option> </select> </div>`, props: ['myprop'], watch: { 'myprop.id': function(newVal, oldVal) { // watch it console.log('Prop changed: ', newVal, ' | was: ', oldVal) } } } } });
 <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script> <div id="app"> <child :myprop="myObj"></child> reading from child: {{myObj.id}} </div>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM