父更新vuejs时子组件不更新

Posted

技术标签:

【中文标题】父更新vuejs时子组件不更新【英文标题】:Child Component not updating when parent updates vuejs 【发布时间】:2017-12-31 12:38:16 【问题描述】:

我有一个将对象传递给子组件的 vue 实例。子组件有一个复选框,单击该复选框时会调用 vue 实例处理的事件,以更新传递给子组件的父对象。基于vue documentation,我认为这会导致子组件更新相关字段。但是,当我单击复选框时,日期字段并没有像我预期的那样更新。在下图中,当我选中管理名称复选框时,我希望会出现当前日期,但我没有看到任何日期。我在这里错过了什么?

设计

父实例

new Vue(
    el: "#evaluations-app",
    data: 
        evaluation: new Evaluation()        
    ,
    methods:         
        updateEmployeeSO: function (newSO, newSODate) 
            this.evaluation.EmployeeSO = newSO;
            this.evaluation.EmployeeSODate = newSODate;
        ,
        updateReviewerSO: function (newSO, newSODate) 
            this.evaluation.ReviewerSO = newSO;
            this.evaluation.ReviewerSODate = newSODate;
        ,
        updateManagementSO: function (newSO, newSODate) 
            this.evaluation.ManagementSO = newSO;
            this.evaluation.ManagementSODate = newSODate;
        
);

子组件

Vue.component('sign-off', 
    props: ['initEvaluation', 'perspective'],
    template: `
        <div class="sign-off-comp">
            <div class="sign-off-item">
                <div class="sign-off-field-1 col-1">evaluation.EmployeeName</div>
                <input :disabled="!enableEmployeeSO" v-model="evaluation.EmployeeSO" class="sign-off-field-2 col-2" type="checkbox" @click="EmployeeSOChanged"/>
                <div class="sign-off-field-3 col-3">employeeSODate</div>                
            </div>  
            <div class="sign-off-item">
                <div class="sign-off-field-1 col-1">evaluation.ReviewerName</div>
                <input :disabled="!enableReviewerSO" v-model="evaluation.ReviewerSO" class="sign-off-field-2 col-2" type="checkbox" @click="ReviewerSOChanged"/>
                <div class="sign-off-field-3 col-3">reviewerSODate</div>                
            </div>   
            <div class="sign-off-item">
                <div class="sign-off-field-1 col-1">evaluation.ManagementName</div>
                <input :disabled="!enableManagementSO" v-model="evaluation.ManagementSO" class="sign-off-field-2 col-2" type="checkbox" @click="ManagementSOChanged"/>
                <div class="sign-off-field-3 col-3">managementSODate</div>                
            </div>                   
        </div>
    `,
    data: function () 
        return 
            evaluation: this.initEvaluation,
            employeeClicked: false,
            reviewerClicked: false,
            managementClicked: false,
            currentCommentSource: this.perspective
        
    ,
    methods: 
        EmployeeSOChanged: function () 
            this.employeeClicked = true;
            //this.evaluation.EmployeeSODate == null || this.evaluation.EmployeeSODate == "" ? this.evaluation.EmployeeSODate = Helpers.getCurrentDate() : this.evaluation.EmployeeSODate = "";
            this.$emit('employee-so-changed', this.evaluation.EmployeeSO, this.evaluation.EmployeeSODate);
        ,
        ReviewerSOChanged: function () 
            this.reviewerClicked = true;
            //this.evaluation.ReviewerSODate == null || this.evaluation.ReviewerSODate == "" ? this.evaluation.ReviewerSODate = Helpers.getCurrentDate() : this.evaluation.ReviewerSODate = "";
            this.$emit('reviewer-so-changed', this.evaluation.ReviewerSO, this.evaluation.ReviewerSODate);
        ,
        ManagementSOChanged: function () 
            this.managementClicked = true;
            //this.evaluation.ManagementSODate == null || this.evaluation.ManagementSODate == "" ? this.evaluation.ManagementSODate = Helpers.getCurrentDate() : this.evaluation.ManagementSODate = "";
            this.$emit('management-so-changed', this.evaluation.ManagementSO, this.evaluation.ManagementSODate == null || this.evaluation.ManagementSODate == "" ? Helpers.getCurrentDate() : "");
        
    ,
    computed: 
        enableEmployeeSO: function () 
            return (this.perspective == "Employee" && !this.evaluation.EmployeeSO) || this.employeeClicked;
        ,
        enableReviewerSO: function () 
            return (this.perspective == "Reviewer" && !this.evaluation.ReviewerSO && this.evaluation.EmployeeSO) || this.reviewerClicked;
        ,
        enableManagementSO: function () 
            return (this.perspective == "Management" && !this.evaluation.ManagementSO && this.evaluation.ReviewerSO && this.evaluation.EmployeeSO) || this.managementClicked;
        ,
        employeeSODate: function () 
            return this.evaluation.EmployeeSODate != null && this.evaluation.EmployeeSODate == new Date("01-01-1900") ? "" : this.evaluation.EmployeeSODate != null && this.evaluation.EmployeeSODate.length >= 10 ? this.evaluation.EmployeeSODate.substring(0, 10) : this.evaluation.EmployeeSODate;
        ,
        reviewerSODate: function () 
            return this.evaluation.ReviewerSODate != null && this.evaluation.ReviewerSODate == new Date("01-01-1900") ? "" : this.evaluation.ReviewerSODate != null && this.evaluation.ReviewerSODate.length >= 10 ? this.evaluation.ReviewerSODate.substring(0, 10) : this.evaluation.ReviewerSODate;
        ,
        managementSODate: function () 
            return this.evaluation.ManagementSODate != null && this.evaluation.ManagementSODate == new Date("01-01-1900") ? "" : this.evaluation.ManagementSODate != null && this.evaluation.ManagementSODate.length >= 10 ? this.evaluation.ManagementSODate.substring(0, 10) : this.evaluation.ManagementSODate;
        
    
);

型号

export class Evaluation 
    private _EmployeeName: string;
    private _EmployeeSO: boolean;
    private _EmployeeSODate: Date;
    private _ReviewerName: string;
    private _ReviewerSO: boolean;
    private _ReviewerSODate: Date;
    private _ManagementReviewerName: string;
    private _ManagementReviewerSO: boolean;
    private _ManagementReviewerSODate: Date;

    constructor() 
        this._EmployeeName = "";
        this._EmployeeSO = false;
        this._EmployeeSODate = new Date("01-01-1900");
        this._ReviewerName = "";
        this._ReviewerSO = false;
        this._ReviewerSODate = new Date("01-01-1900");
        this._ManagementReviewerName = "";
        this._ManagementReviewerSO = false;
        this._ManagementReviewerSODate = new Date("01-01-1900");
    

    get EmployeeName(): string 
        return this._EmployeeName;
    
    set EmployeeName(employeeName: string) 
        if (this._EmployeeName != employeeName) 
            this._EmployeeName = employeeName;
        
    

    get EmployeeSO(): boolean 
        return this._EmployeeSO;
    
    set EmployeeSO(employeeSO: boolean) 
        if (this._EmployeeSO != employeeSO) 
            this._EmployeeSO = employeeSO;
        
    

    get EmployeeSODate(): Date 
        return this._EmployeeSODate;
    
    set EmployeeSODate(employeeSODate: Date) 
        if (this._EmployeeSODate != employeeSODate) 
            this._EmployeeSODate = employeeSODate;
        
    

    get ReviewerName(): string 
        return this._ReviewerName;
    
    set ReviewerName(reviewerName: string) 
        if (this._ReviewerName != reviewerName) 
            this._ReviewerName = reviewerName;
        
    

    get ReviewerSO(): boolean 
        return this._ReviewerSO;
    
    set ReviewerSO(reviewerSO: boolean) 
        if (this._ReviewerSO != reviewerSO) 
            this._ReviewerSO = reviewerSO;
        
    

    get ReviewerSODate(): Date 
        return this._ReviewerSODate;
    
    set ReviewerSODate(reviewerSODate: Date) 
        if (this._ReviewerSODate != reviewerSODate) 
            this._ReviewerSODate = reviewerSODate;
        
    

    get ManagementReviewerName(): string 
        return this._ManagementReviewerName;
    
    set ManagementReviewerName(managementReviewerName: string) 
        if (this._ManagementReviewerName != managementReviewerName) 
            this._ManagementReviewerName = managementReviewerName;
        
    

    get ManagementReviewerSO(): boolean 
        return this._ManagementReviewerSO;
    
    set ManagementReviewerSO(managementReviewerSO: boolean) 
        if (this._ManagementReviewerSO != managementReviewerSO) 
            this._ManagementReviewerSO = managementReviewerSO;
        
    

    get ManagementReviewerSODate(): Date 
        return this._ManagementReviewerSODate;
    
    set ManagementReviewerSODate(managementReviewerSODate: Date) 
        if (this._ManagementReviewerSODate != managementReviewerSODate) 
            this._ManagementReviewerSODate = managementReviewerSODate;
        
    

更新

我刚刚注意到,在我的子组件中,我使用了MangementSOManagementSODate,而模型使用了ManagementReviewerSOManagementReviewerSODate。更改这些修复了我的代码。但是,基于下面的讨论,我有点困惑,为什么将 props 放入本地数据是处理这种情况的不正确方法。有人可以解释一下吗?

【问题讨论】:

不要在数据函数中存储传递下来的属性,而不是evaluation.ReviewerName,只需使用initEvaluation.ReviewerName @AndreyKudriavtsev 为什么我应该避免存储传递下来的属性?在这种情况下,我将同时更改子组件中的 [type]SO 和 [type]SODate 字段。如果我不将这些存储在数据中,那么我将根据文档更新会引发错误的道具。 因为您破坏了该属性的“反应性”。您应该直接在方法和计算属性中使用属性,而不是使用本地数据,就像使用 this.evaluation.ReviewerSODate 使用 this.initEvaluation.ReviewerSODate 一样。您不会丢失任何东西,只需将一个变量替换为另一个变量即可。 【参考方案1】:

用props初始化数据属性没有错。我认为这里的许多 cmets 中最大的混淆来源是initEvaluation 是一个对象。在这种情况下,对该对象的任何更改都将反映在使用该对象的任何地方

在有问题的代码中,父组件中的evaluation 和子组件中的evaluation是同一个对象。对该对象所做的更改反映在父对象中,并且 Vue 不会 no 抱怨,因为您在技术上并没有改变对对象的引用,您只是在更改它的属性的值。

通常,如果您将 primitive 值作为属性传递,Vue 会发出警告。假设您传递了一个字符串值,然后将该值与v-model 一起使用。在那种的情况下,Vue 会抛出一个警告(在开发版本中)你正在改变一个属性。警告有两个原因;首先是因为该值没有传播到父级(这可能是意外行为),其次是因为每当 父级 中的数据发生更改时,它都会覆盖孩子。

但是,当对象或数组作为属性传递时,Vue 只会在您更改对象引用时报错。例如,在您的代码中,如果您要这样做:

this.initEvaluation = new Evaluation()

Vue 会抱怨你正在改变一个属性。但是,在您的代码中,您只是更改了initEvaluationproperties,这不会导致警告,并且还会导致这些值在 任何地方 反映出来,因为您是随处更新相同的对象。

其中一个后果是,在您的代码中有效地设置 evaluation: this.initEvaluation 是虚假的。您可以在模板中使用initEvaluation 并获得与使用evaluation 相同的结果。同样,这是因为它们是 same 对象。这是路易斯试图解释的部分内容。在这种情况下,路易斯的第一句话有点误导。数据函数只调用once,这意味着用属性初始化的数据只会接收一次属性的值。但是,因为 initEvaluation 是一个对象,所以在问题的代码中并不重要。对象 reference 永远不会改变,只有属性会改变。如果父级出于某种原因更改引用,则子级中的evaluation不会使用新的引用进行更新。 p>

对于一个对象是否希望立即反映更新是否值得商榷。在许多情况下,您希望控制何时 更新发生。在这些情况下,您可能会执行类似evaluation: Object.assign(, this.initEvaluation) 的操作,它会生成initEvaluation 对象的副本。这样做的好处是您可以对子组件中的对象进行任意数量的更改,而这些更改不会反映在组件外部。然后,在您验证所有更改都正确后,您可以发出这些更改。

【讨论】:

【参考方案2】:

当您基于 propsinitEvaluationperspective)创建 data 属性时,它们不再依赖于道具,并且您仅引用数据,就好像它们只是副本一样,但不是收到的实际道具。所以,他们不会对道具做出反应。

由于您似乎没有在组件中更新它们,您可以直接引用props,而不是从props 创建的data

编辑:从头开始,你在evaluation 上使用v-model,所以如果你直接引用它们,你会更改道具。

所以,一般来说,我会从数据中删除它:

evaluation: this.initEvaluation
// ...
currentCommentSource: this.perspective

并直接引用props,并按照文档中的建议将v-model 替换为:value@input

【讨论】:

根据我的经验,这似乎并不准确。过去我已经传递了属性并将它们保存到数据中,并且在更新父母的数据时看到了道具的变化。事实上,我刚刚意识到这个问题我有一个拼写错误,修复后会显示这种行为。 是的,当父母的数据更新时,道具会发生变化。但是您没有在任何地方使用道具,当您将它们分配给数据中的属性时,该数据本身对道具没有反应,因此道具可能会改变,但从道具派生的数据不会。这是 vue 中的预期行为 这不是我所看到的。查看我更新的问题。当我将ManagementSO 更改为ManagementReviewerSO 并将ManagementSODate 更改为ManagementReviewerSODate 时,一切正常,这似乎与您所说的相矛盾。我在这里错过了什么? 查看pen 以获取示例。但是如果它现在正常工作,那么当它是一个胖对象时,vue 可能会保留引用,因为它是在数据中返回的。有趣,我会重新检查文档。

以上是关于父更新vuejs时子组件不更新的主要内容,如果未能解决你的问题,请参考以下文章

道具更改时子组件不会更新

vuejs 从子组件更新父数据

vuejs 从子组件更新父数据

父更新时VUE Js子不更新

为啥 vuejs 子组件数据更改更新父数据也没有显式 $emit?

VueJS - 在渲染子组件之前等待父组件中的更新()