Vue.js 如何强制计算属性重新计算?

37

这是我的组件计算属性:

methods: {
  addFavoritePlace(place_id) {
    axios.post('/api/add-favorite-place', { place_id: place_id })
      .then(response => {
        // I need here force command.
      });
  },
},

computed: {
  filteredPlaces: function () {
    var is_open;

    if (this.showOpened) {
      is_open = 'open'
    } else {
      is_open = 'close'
    }

    return this.singlePlaces.filter((j) => {
        return j.name.toLowerCase().match(this.search.toLowerCase();
    });
  }
}

我的标记:

<span @click="addFavoritePlace(place.id)" class="favorite-btn active" v-if="place.is_favorited == true">
  <i class="icon-heart"></i>
</span>

<span @click="addFavoritePlace(place.id)" class="favorite-btn" v-else>
  <i class="icon-heart"></i>
</span>

我需要再次在computed内调用过滤函数,以激活收藏按钮。


1
当数据发生变化时,属性会自动重新计算,因此您不应该“重新计算”属性。 在我看来,您只需要更新原始列表this.singlePlaces中的数据即可。 - Thomas Ferro
我尝试过这个。但是它并没有更新数据,而是再次添加了数据。 - Murat Sağlık
你是先找到索引再更新元素,还是直接添加了一个新条目? - Thomas Ferro
数据库中的数据已更新。但它重新添加了未更新的状态。 - Murat Sağlık
我指的是“在Vue.js中更新元素”。您的“post”应该返回更新后的值,只需将其分配给同一索引处的数组,您的计算属性就会自动更新。 - Thomas Ferro
请注意,Vue无法检测属性的添加或删除:https://vuejs.org/v2/guide/reactivity.html#Change-Detection-Caveats - Dylan Smith
4个回答

43
如果您想要强制更新计算属性并重新计算其值,您可以简单地使用数据属性,并在计算函数中提及该属性(您不必使用它,只需存在即可),然后更改该数据属性;这将强制计算出新的值。
以下是代码:
data() {
  return {
    refreshKey: 0,
    // ...
  }
},
computed: {
  myComputedValue() {
    // just mentioning refreshKey
    this.refreshKey;

    // rest of the function that actualy do the calculations and returns a value
  },
},
methods: {
  myMethod() {
    // the next line would force myComputedValue to update
    this.refreshKey++;

    // the rest of my method
  },
},


2
最好使用数字并递增它,而不是布尔值。在您的情况下,如果myMethod在同一时刻被调用两次,则该值不会更改,并且计算不会重新评估。 - Steven Spungin
@StevenSpungin,您完全正确,谢谢!我会更新答案。 - mo3n
1
太棒了!修复了我的 bug。 - Dazzle
这样做虽然非常好,但我认为应该有更推荐的方法。在需要computed propertiescache功能的边缘情况下,我们需要能够强制重新计算,即使用computed来存储元素HTML,而不检测更改。 - Huy Phạm

3

不需要强制更新计算值。如果所需的值更改,计算属性将自动重新计算。

因此,在您的情况下,您需要更改singlePlaces,然后filteredPlaces将自动更新。

关于数组和对象的响应性,文档中有一些需要注意的事项(https://v2.vuejs.org/v2/guide/reactivity.html#For-Arrays)。

在您的示例中,您没有包含添加收藏夹地点的代码,但您可能需要执行类似以下操作:

Vue.set(this.singlePlaces, indexOfItem, updatedItem)

我本来想问:如果该值是基于localStorage中的一个值设置的,那怎么办呢?但似乎我可以在localStorage.getItem周围加上花括号,然后它就会更新。 - Herbert Van-Vliet
1
这是使用Vue响应性的正确答案,因为它可以在没有任何hack的情况下正常工作。 - Heroselohim
@HerbertVan-Vliet,您能否解释一下您的评论?我正在使用sessionStorage,但是显然我的计算属性在set()之后没有重新计算。 - Witold
@Witold:抱歉,我找不到我做了什么。我可能在某个时候升级以从localStorage按需获取。但是使用{sessionStorage}会给你什么? - Herbert Van-Vliet
@HerbertVan-Vliet 你是指像这样吗?{sessionStorage}.getItem( 这会导致错误 {(intermediate value)}.getItem不是一个函数 没关系,我可以使用refresh-data-item。我只是好奇而已。 - Witold
可能是类似于 {sessionStorage.getItem('something')} 的东西,但现在不确定了,抱歉。 - Herbert Van-Vliet

2

首先,您可以通过切换类来节省两倍的事件处理、节点创建和重新计算和重绘。此外,我不确定在@ click事件中调用函数是否会立即触发此函数,并且只会将其包装成匿名函数。另外,请注意,我将整个place对象引用传递给addFavoritePlace,而不是place.id

<span
    @click="() => addFavoritePlace(place)"
    class="favorite-btn"
    :class="{active: place.is_favorited}">
        <i class="icon-heart"></i>
</span>

其次,filteredPlaces的一部分代码没有意义,因为您设置了一个在其他地方未使用且未返回的变量,所以垃圾收集器会立即将其删除。相反,您可以采用以下方式:
computed: {
  filteredPlaces () {
    const search = this.search.toLowerCase()
    return this.singlePlaces.filter((p) => {
        return p.name.toLowerCase().includes(search)
    });
  }
}

最后,您只需更新singlePlaces项目即可自动影响filteredPlaces,无需进行任何特殊操作。如果您没有在每个place中拥有is_favorited属性,则可以在初始化时将其map或强制Vue使用set方法跟踪其更新。另外,正如我所说,我会传递整个place引用而不是id,这样我们就不必再次在堆栈中查找该元素了:
methods: {
  async addFavoritePlace (place) {
    await axios.post('/api/add-favorite-place', { place_id: place.id })
    this.$set(place, 'is_favorited', true)
  },
},

1
这是我对此的看法,使用组合式API:

useRefreshable.ts

import { ref } from "vue";

export function useRefreshable<T>(getter: () => T): {
  getter(): T;
  refresh(): void;
} {
  const refreshKey = ref(0);

  return {
    getter() {
      refreshKey.value;
      return getter();
    },
    refresh() {
      refreshKey.value++;
    },
  };
}

useRefreshable.test.ts

import { computed, nextTick } from "vue";
import { useRefreshable } from "@/composition/useRefreshable";

describe("useRefreshable", () => {
  test("", async () => {
    let notReactiveValue = 1;

    function getValue() {
      return notReactiveValue;
    }

    const normalComputed = computed(getValue);

    const refreshable = useRefreshable(getValue);
    const refreshableComputed = computed(refreshable.getter);

    expect(normalComputed.value).toEqual(1);
    expect(refreshableComputed.value).toEqual(1);

    notReactiveValue++;
    await nextTick();

    expect(normalComputed.value).toEqual(1);
    expect(refreshableComputed.value).toEqual(1);

    refreshable.refresh();
    await nextTick();

    expect(normalComputed.value).toEqual(1);
    expect(refreshableComputed.value).toEqual(2);
  });
});

您可以以多种方式进行包装,例如:

import { computed, ComputedRef } from "vue";
import { useRefreshable } from "@/composition/useRefreshable";

export function useRefreshableComputed<T>(getter: () => T): {
  readonly comp: ComputedRef<T>;
  refresh(): void;
} {
  const refreshable = useRefreshable(getter);
  return {
    comp: computed(() => refreshable.getter()),
    refresh: refreshable.refresh,
  };
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接