【问题标题】:Vue.js computed property loses its reactivity when passed through an eventVue.js 计算属性在通过事件时失去反应性
【发布时间】:2018-08-21 21:22:58
【问题描述】:

我的主应用程序中有一个 Modal 组件,每当必须显示模式时,它都会通过事件获取传递的内容。模态内容始终是一个列表,其中包含与每个项目关联的操作,例如“选择”或“删除”:

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;
    }
  }
});

遗憾的是,当像下面的组件一样将计算属性传递给该模式时,反应链接会被破坏 -> 如果操作是“删除”,则列表不会更新。

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);
    }
  }
});

查看此小提琴中的最小用例:https://jsfiddle.net/christophfriedrich/cm778wgj/14/

我认为这是一个错误 - Vue 不应该记住 objKeys 用于在 Modal 中进行渲染并更新它吗? (将obj 更改为objKeys 的转发有效。)如果没有,我哪里做错了,我怎样才能达到我想要的结果?

【问题讨论】:

  • 您正在删除对象属性,vue 不监视...您应该添加一个监视程序
  • 我不认为这是一个错误。确实需要一个观察者。
  • 两个组件之间的观察者或共享状态:vuejs.org/v2/guide/…
  • @samayo Vue 在我使用 $delete 函数时确实注意到了这一点 - 至少我是这么理解的 vuejs.org/v2/api/#Vue-delete 你看我的小提琴了吗? comp 的模板中的渲染更新得非常好。

标签: javascript vue.js vuejs2 vue-reactivity


【解决方案1】:

您的模态使用它自己的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;
    }
  }

该副本在调用show 时生成一次,而您要复制的只是您发出showModal 事件时计算的值。 show 接收的不是计算的,它分配的也不是计算的。这只是一个值。

如果您在代码中的任何地方进行了类似的分配

someDataItem = someComputed;

数据项不会是计算的功能副本,它将是分配时其值的快照。这就是为什么在 Vue 中复制值是一种不好的做法:它们不会自动保持同步。

您可以传递一个返回感兴趣值的函数,而不是到处复制值;实际上是一个get函数。为了语法清晰,您可以根据该函数进行计算。那么你的代码就变成了

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】:

    您将值传递给函数,而不是将道具传递给组件。道具是反应性的,但值只是值。您在comp 的模板中包含modal,因此重新修改它以(至少)将items 作为道具。然后它将是反应性的。

    我建议让删除过程遵循 emit-event-and-process-in-parent 而不是传递回调。

    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>

    【讨论】:

    • 我希望能够从代码中的任何一点触发模式,因此将items 作为道具传递并不能真正解决我的问题。此外,当我直接传递obj(而不是根据它计算出的objKeys 属性)时,一切都按我的预期工作,请参阅this slightly changed fiddle。虽然我在技术上仍然只是将值传递给函数。
    • 嗯,当然,包含反应元素的对象仍然包含反应元素。但是对象是通过引用而不是值传递的。 (从技术上讲,它是按值传递的,但值 引用。)
    • 我的困惑仍然存在,因为我认为计算属性和普通属性应该表现相同。但是,您的其他答案确实为我的目的很好地解决了这个问题,所以我将此问题标记为已解决,并询问 a new one 其他方面,受到您(同时已删除?)其他评论的鼓励。
    猜你喜欢
    • 2019-08-08
    • 2017-08-26
    • 1970-01-01
    • 2021-04-20
    • 2020-05-07
    • 2017-10-06
    • 2017-10-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多