VueJS 在不更改父数据的情况下编辑道具的正确方法

Posted

技术标签:

【中文标题】VueJS 在不更改父数据的情况下编辑道具的正确方法【英文标题】:VueJS right way to edit prop without changing parent data 【发布时间】:2017-12-01 03:08:22 【问题描述】:

在我的父 vue 组件中,我有一个 user 对象。

如果我将该用户对象作为道具传递给子组件:

<child :user="user"></child>

在我的子组件中我更新user.name,它也会在父组件中更新。

我想编辑子组件中的用户对象,而不会将更改反映在父组件中的用户对象中。

有没有比克隆对象更好的方法:JSON.parse(JSON.stringify(obj))

【问题讨论】:

【参考方案1】:

您不必使用JSON 对象。

const child = 
  props:["user"],
  data()
    return 
      localUser: Object.assign(, this.user)
    
  

在您的孩子体内使用localUser(或任何您想称呼的名称)。

编辑

我修改了一个为这个问题的另一个答案创建的小提琴,以展示上述概念并询问@user3743266

我自己正在处理这个问题,我发现它非常 有用。你的例子效果很好。在孩子中,您创建了一个 数据中的元素获取道具的副本,并且孩子可以工作 与副本。有趣且有用,但是...我不清楚 当本地副本被更新时,如果有其他东西修改了 父母。我修改了你的小提琴,删除了 v-ifs 所以一切都是 可见,并复制编辑组件。如果您在其中修改名称 组件,另一个是孤立的,没有变化?

当前组件如下所示:

Vue.component('edit-user', 
  template: `
  <div>
    <input type="text" v-model="localUser.name">
    <button @click="$emit('save', localUser)">Save</button>
    <button @click="$emit('cancel')">Cancel</button>
  </div>
  `,
  props: ['user'],
  data() 
    return 
      localUser: Object.assign(, this.user)
    
  
)

因为我做出了使用用户的本地副本的设计决定,@user3743266 是正确的,组件不会自动更新。 属性 user 已更新,但 localUser 未更新。在这种情况下,如果您想在属性更改时自动更新本地数据,您需要一个观察者。

Vue.component('edit-user', 
  template: `
  <div>
    <input type="text" v-model="localUser.name">
    <button @click="$emit('save', localUser)">Save</button>
    <button @click="$emit('cancel')">Cancel</button>
  </div>
  `,
  props: ['user'],
  data() 
    return 
      localUser: Object.assign(, this.user)
    
  ,
  watch:
    user(newUser)
        this.localUser = Object.assign(, newUser)
    
  
)

这是更新后的fiddle。

这使您可以完全控制何时/是否更新或发出本地数据。例如,您可能希望在更新本地状态之前检查条件。

  watch:
    user(newUser)
      if (condition)
        this.localUser = Object.assign(, newUser)
    
  

正如我在其他地方所说,有时您可能希望利用对象属性的可变性,但有时您可能希望获得更多控制权。

【讨论】:

@user3743266 添加了一些解释。 非常好。这很有趣,谢谢。 Vue 震撼了我的祖母。我认为 Evan You 实际上是一个由 30 名火箭科学家组成的团队。 你确定吗?我会说这个线程和你的答案有很多价值。像这样的问题可能与使用 vue 的每个人有关。离开它,它可能会有一个漫长而令人惊讶的生命。 Object.assign 不会深度复制整个对象(似乎只是第一层)。所以像object.key1.key2 这样的嵌套属性在父子节点中都会发生变化。一种解决方法是使用localUser: JSON.parse(JSON.stringify(this.user))【参考方案2】:

在上述解决方案中,watcher 在第一次绑定时不会触发,只会在 prop 更改时触发。要解决这个问题,请使用 immediate=true

watch: 
  test: 
    immediate: true,
    handler(newVal, oldVal) 
      console.log(newVal, oldVal)
    ,
  ,

【讨论】:

【参考方案3】:

您可以拥有一个数据变量,其中包含您希望在本地可编辑的信息并在创建的方法中加载值

data() 
return  localUserData: name: '', (...)

(...)
created() 
    this.localUserData.name = this.user.name;

这样您就可以清楚地知道您正在编辑哪些数据。根据需要,您可能希望有一个观察者来更新 localData,以防用户属性发生变化。

【讨论】:

【参考方案4】:

According to this,孩子“不能”和“不应该”修改父母的数据。但是here 您可以看到,如果父级将一些反应性数据作为属性传递给子级,则它是通过引用传递的,并且父级会看到子级的更改。这可能是您大部分时间想要的,不是吗?您只是在修改父级明确共享的数据。如果您希望孩子拥有user 的独立副本,您可以使用 JSON.parse(JSON.stringify()) 执行此操作,但请注意您将序列化 Vue 注入的属性。你什么时候做?还记得 props 是响应式的,因此父级可以随时发送新用户,从而消除本地更改?

也许您需要更多地告诉我们您为什么希望孩子拥有自己的副本?孩子将如何处理它的副本?如果孩子的用户以某种系统的方式从父用户派生(大写所有文本或其他内容),请查看计算属性。

【讨论】:

大多数时候这不是你想要的,不。如果属性值发生更改,本地数据将不会被清除(除非您专门设置它这样做)。您不希望子项发生更改的原因有很多立即反映在父母身上。考虑某人想要取消他们的更改的情况。如果您对属性中传递的数据进行更改,您必须想出一些额外的解决方案来取消所有这些更改。如果您先复制一份,并且仅在 $emit 数据时对父项进行更改,那么您可以随时取消。 您说数据不会被擦除,但这正是我的jsfiddle 中发生的情况,如果您将反应数据作为属性传递给孩子。父母和孩子共享一个事实来源?您将如何实现您的撤消方案? 我说的是 data,就像在数据属性中一样,而不是属性本身。我更新了你的fiddle。请注意,现在,您可以轻松取消对子项所做的更改,因为子项仅在您打算时才发出更改。如果您直接编辑了用户属性并且想要取消,则必须执行一些操作,例如保存原始状态并恢复它。我同意你的观点,方便你可以改变对象和数组属性,但也不太可维护。当你改变一个属性时,你应该小心。它不应该是您的默认设置。 我自己来解决这个问题,我发现这非常有用。你的例子效果很好。在孩子中,您在data 中创建了一个元素,该元素获取道具的副本,并且孩子使用该副本。有趣且有用,但是...如果 else 修改了父项,我不清楚本地副本何时更新。 I modified your fiddle,删除 v-ifs 以使所有内容都可见,并复制编辑组件。如果您在一个组件中修改name,另一个是孤立的并且没有任何更改?

以上是关于VueJS 在不更改父数据的情况下编辑道具的正确方法的主要内容,如果未能解决你的问题,请参考以下文章

vuejs 计算属性 - 何时触发更新?

如何在不复制片段的情况下传递道具

如何使用 Typescript 在不丢失任何道具的情况下键入样式组件?

vuejs 从子组件更新父数据

vuejs 从子组件更新父数据

在不刷新父页面的情况下更改 iframe src