Vue 组件 prop 更改不会触发重新渲染

Posted

技术标签:

【中文标题】Vue 组件 prop 更改不会触发重新渲染【英文标题】:Vue component prop change does not trigger rerender 【发布时间】:2018-10-14 00:51:21 【问题描述】:

在我的 Vue 2 应用程序中,我有一个大对象,它作为道具从根组件向下传递到一些子组件。当我更改对象的某些属性时,子组件应该更新并重新呈现。在某些情况下,他们这样做,在其他情况下,他们没有。我需要一些帮助来找出它为什么不起作用。

这是一个不更新的子组件:

<template>
<div class="upgradeBar">
    level
    <div
            v-for="lvlNum in maxLevel + 1"
            class="level"
            v-bind:class="reached: isLevelReached(lvlNum - 1)"
    ></div>

    <button
            class="btnUpgrade"
            @click="onLevelUp()"
            v-if="!isLevelReached(maxLevel)"
    >
        +
    </button>
</div>
</template>


<script lang="ts">
import Component, Prop, Vue from 'vue-property-decorator';
import Upgradable from "../../../models/Upgradable";

@Component()
export default class UpgradeBar extends Vue 
    name: 'UpgradeBar';

    @Prop() entity: Upgradable;

    get level(): number 
        return this.entity.level;
    

    get maxLevel(): number 
        return this.entity.MAX_LEVEL;
    

    onLevelUp() 
        this.entity.levelUp();
    

    isLevelReached(level: number): Boolean 
        return this.entity.level >= level;
    

</script>

组件是这样调用的:

<UpgradeBar :entity="entity" />

所有代码都有效。当我单击btnUpgrade 按钮时,entity.level 确实发生了变化,但我需要关闭并重新打开组件才能看到更改。浏览器开发工具也不会立即显示更改。我需要点击组件来刷新调试器中的值。

编辑: entity 类看起来基本上是这样的(摘录):

class Entity 
    name: string = 'some name';
    level: number = 1;

我进行了更深入的搜索,似乎可以归结为:对象的某些属性是反应性的(它们具有由 vue 创建的 getter/setter)而有些则没有。 entity.name 有一个设置器,因此更改它会更新组件。 entity.level 没有。问题来了:为什么他们会受到不同的对待?这是一个日志:

【问题讨论】:

添加了实体类。将entity.levelentity._level 添加到模板没有帮助。 这种使用模型(具有内部状态和功能)的模式在 Vue 中并不常见。我不确定将类作为道具传递是否会出现一些意想不到的问题。 显然它有意想不到的问题,虽然它在其他地方工作得很好:) 你有什么建议?将简单的值作为道具传递? 我的建议是使用 Vuex(flux 模式,单一存储管理),您基本上可以将所有数据放在一个位置(如内部 noSQL 文档存储),而不是传递对象实例,您可以使用全局可访问的函数(操作/getter)从任何地方管理单个实例。调试起来会更容易,例如,它为您提供了一种逐步浏览状态历史的方法。但是,您仍然需要使用Vue.$set 【参考方案1】:

如果没有看到entity.levelUp 的代码就无法确定,但这似乎是一个反应性问题,可以通过在该函数中使用Vue.$set 来解决。

您可以通过在this.entity.levelUp(); 之后添加this.$forceUpdate(); 来确认这种情况

更新

this._level = this._level + 1;

可以改成

Vue.$set(this, _level, this._level + 1); 您需要在该组件/文件中导入 Vue 才能访问 $set 函数

【讨论】:

this.$forceUpdate(); 确实有帮助。那么我需要如何更改代码以使 forceUpdate 变得不必要呢? 你应该删除 forceUpdate 来测试。保留 forceUpdate 会影响性能,因为它会对所有对象进行全面检查,而不是使用反应性来选择性地更新。这个vuejs.org/v2/guide/reactivity.html值得一读(读了好几遍才明白) 所以是的,这是一个反应性问题。我添加了对象的console.log。很少有属性是反应性的。 name 就是其中之一。 level 不是,尽管它的处理方式完全相同。怎么可能?【参考方案2】:

您没有显示(或者我找不到)更改对象的代码,但是您是否使用$set()Vue.set() 而不是直接更改对象的属性?由于reactivity limitations

,直接更改属性通常不起作用

编辑添加:

我现在明白了。我想你想要这样的东西:

this.$set(this, '_level', this._level + 1);

【讨论】:

以上是关于Vue 组件 prop 更改不会触发重新渲染的主要内容,如果未能解决你的问题,请参考以下文章

state 用作组件中的 props,状态更改不会重新渲染组件

React parent pass children 将触发重新渲染而不更改 state/props

Vuex 全局状态更改不会触发 Nuxt 中 v-for 循环中的重新渲染组件

React生命周期, setState、props改变触发的钩子函数

尽管状态发生了变化,但自定义钩子不会触发组件重新渲染

vue和react中props变化后修改state的方式