Vue.js computed property loses its reactivity when passed through an event

怎甘沉沦 提交于 2019-12-12 16:07:25

问题


I have a Modal component in my main app that gets passed content via an event whenever a modal has to be shown. Modal content is always a list with an action associated with each item, like "select" or "remove":

Vue.component('modal', {
  data() {
    return {
      shown: false,
      items: [],
      callback: ()=>{}
    }
  },
  mounted() {
    EventBus.$on('showModal', this.show);
  },
  template: `<ul v-if="shown">
    <li v-for="item in items">
      {{ item }} <button @click="callback(item)">Remove</button>
    </li>
  </ul>`,
  methods: {
    show(items, callback) {
      this.shown = true;
      this.items = items;
      this.callback = callback;
    }
  }
});

Sadly, when passing a computed property to that modal like in the component below, the reactive link gets broken -> if the action is "remove", the list is not updated.

Vue.component('comp', {
  data() {
    return {obj: {a: 'foo', b: 'bar'}}
  },
  computed: {
    objKeys() {
      return Object.keys(this.obj);
    }
  },
  template: `<div>
    <button @click="showModal">Show Modal</button>
    <modal></modal>
  </div>`,
  methods: {
    remove(name) {
      this.$delete(this.obj, name);
    },
    showModal() {
      EventBus.$emit('showModal', this.objKeys, this.remove);
    }
  }
});

See the minimal use case in this fiddle: https://jsfiddle.net/christophfriedrich/cm778wgj/14/

I think this is a bug - shouldn't Vue remember that objKeys is used for rendering in Modal and update it? (The forwarding of the change of obj to objKeys works.) If not, what am I getting wrong and how could I achieve my desired result?


回答1:


You have the modal working with its own copy of items:

 template: `<ul v-if="shown">
    <li v-for="item in items">
      {{ item }} <button @click="callback(item)">Remove</button>
    </li>
  </ul>`,
  methods: {
    show(items, callback) {
      this.shown = true;
      this.items = items;
      this.callback = callback;
    }
  }

That copy is made once, upon the call to show, and what you are copying is just the value of the computed at the time you emit the showModal event. What show receives is not a computed, and what it assigns is not a computed. It's just a value.

If, anywhere in your code, you made an assignment like

someDataItem = someComputed;

the data item would not be a functional copy of the computed, it would be a snapshot of its value at the time of the assignment. This is why copying values around in Vue is a bad practice: they don't automatically stay in sync.

Instead of copying values around, you can pass a function that returns the value of interest; effectively a get function. For syntactic clarity, you can make a computed based on that function. Then your code becomes

const EventBus = new Vue();

Vue.component('comp', {
  data() {
    return {
      obj: {
        a: 'foo',
        b: 'bar'
      }
    }
  },
  computed: {
    objKeys() {
      return Object.keys(this.obj);
    }
  },
  template: `<div>
    <div>Entire object: {{ obj }}</div>
    <div>Just the keys: {{ objKeys }}</div>
    <button @click="remove('a')">Remove a</button>
    <button @click="remove('b')">Remove b</button>
    <button @click="showModal">Show Modal</button>
    <modal></modal>
  </div>`,
  methods: {
    remove(name) {
      this.$delete(this.obj, name);
    },
    showModal() {
      EventBus.$emit('showModal', () => this.objKeys, this.remove);
    }
  }
});

Vue.component('modal', {
  data() {
    return {
      shown: false,
      getItems: null,
      callback: () => {}
    }
  },
  mounted() {
    EventBus.$on('showModal', this.show);
  },
  template: `<div v-if="shown">
  <ul v-if="items.length>0">
    <li v-for="item in items">
      {{ item }} <button @click="callback(item)">Remove</button>
    </li>
  </ul>
  <em v-else>empty</em>
</div>`,
  computed: {
    items() {
      return this.getItems && this.getItems();
    }
  },
  methods: {
    show(getItems, callback) {
      this.shown = true;
      this.getItems = getItems;
      this.callback = callback;
    }
  }
});

var app = new Vue({
  el: '#app'
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <comp></comp>
</div>



回答2:


You are passing a value to a function, you are not passing a prop to a component. Props are reactive, but values are just values. You include modal in the template of comp, so rework it to take (at least) items as a prop. Then it will be reactive.

I would recommend having the remove process follow the emit-event-and-process-in-parent rather than passing a callback.

const EventBus = new Vue();

Vue.component('comp', {
  data() {
    return {
      obj: {
        a: 'foo',
        b: 'bar'
      }
    }
  },
  computed: {
    objKeys() {
      return Object.keys(this.obj);
    }
  },
  template: `<div>
    <div>Entire object: {{ obj }}</div>
    <div>Just the keys: {{ objKeys }}</div>
    <button @click="remove('a')">Remove a</button>
    <button @click="remove('b')">Remove b</button>
    <button @click="showModal">Show Modal</button>
    <modal :items="objKeys" event-name="remove" @remove="remove"></modal>
  </div>`,
  methods: {
    remove(name) {
      this.$delete(this.obj, name);
    },
    showModal() {
      EventBus.$emit('showModal');
    }
  }
});

Vue.component('modal', {
  props: ['items', 'eventName'],
  data() {
    return {
      shown: false,
    }
  },
  mounted() {
    EventBus.$on('showModal', this.show);
  },
  template: `<div v-if="shown">
  <ul v-if="items.length>0">
    <li v-for="item in items">
      {{ item }} <button @click="emitEvent(item)">Remove</button>
    </li>
  </ul>
  <em v-else>empty</em>
</div>`,
  methods: {
    show(items, callback) {
      this.shown = true;
    },
    emitEvent(item) {
      this.$emit(this.eventName, item);
    }
  }
});

var app = new Vue({
  el: '#app'
})
<script src="//unpkg.com/vue@latest/dist/vue.js"></script>
<div id="app">
  <comp></comp>
</div>


来源:https://stackoverflow.com/questions/49259249/vue-js-computed-property-loses-its-reactivity-when-passed-through-an-event

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!