在 Vue 中将对象属性与子组件同步

Posted

技术标签:

【中文标题】在 Vue 中将对象属性与子组件同步【英文标题】:Syncing an object property with child components in Vue 【发布时间】:2019-12-01 20:56:09 【问题描述】:

背景

我正在使用 Typescript 3.5.3 中基于类的组件在 Vue.js 2.6.10 中构建一个包含大量输入的大型表单。 (在下面的代码 sn-ps 中,我已将其转换为 JS,因为我确信 TS 不是问题。)为了不让用户不知所措,表单被分解为多个组件;一次只向用户显示一个。

父组件

<template>
    <form>
        <Basic :Container.sync="Container"/>
        <!-- More custom components -->
        <output>Container.name</output>
    </form>
</template>

Container 对象是要收集的所有数据的包装器。您会看到我将整个对象传递给子组件,而不是只传递请求给子组件的属性。这似乎是固执己见,但我有我的理由。

Form 组件归结为:

// Imports left out for brevity
@Component( components:   Basic, , )
export default class Form extends Vue 
    private Container
    async created() 
        this.Container = 
            name: null
        
        setInterval(()=>console.log(this.Container.name), 5000)
        // Logs whatever will be entered in the input in the Basic component
    

子组件

引用的组件Basic 是一个普通的输入元素:

<template>
    <input v-model="Container.name" @input="emitUpdate">
</template>

使用同样简单的组件定义:

// Imports left out for brevity
@Component()
export default class Basic extends Vue 
    @Prop() Container
    emitUpdate() 
        console.log(this.Container.name) // Logs whatever was entered in the input
        this.$emit('update:container', this.Container)
    

问题

我期望看到的是要更新的容器,以及第一个 html sn-p 中的 &lt;output&gt; 以显示当前名称。然而,即使变量发生了变化,它也不会,正如两个控制台日志所证明的那样,它们都正确地注册了输入值。

似乎 Vue 的响应性无法将更改注册到 Container 对象,因此不会重新渲染任何 HTML。我该如何解决这个问题?

尝试的解决方案

我尝试使用&lt;Basic … :Container.sync="Container" @Container="Container = $event" /&gt; 明确监听更新事件,但无济于事。

考虑到 Vue 可能不会对对象属性的变化做出反应(毕竟,观察对象也需要设置 deep 选项),我还尝试单独传递 Container 的属性,导致以下结果变化:

在表单的 html 中object is used as the argument to v-bind(我尝试过使用和不使用@update:name):

<Basic … v-bind.sync="Container" @update:name="name=$event"/>

在子组件中,我已更改为 getter 和 setter,以避开“Thou shall not change Props”警告:

<input v-model="Name" @input="emitUpdate">`
@Prop() name
get Name() return this.name 
set Name(newName) this.$emit('update:name', newName)

【问题讨论】:

我可以使用纯 'JS' 编写解决方案,但不能使用 TS,我应该吗? @EvilArthas 是的,请这样做。问题很可能不是源于打字稿。 【参考方案1】:

在我看来,这里的问题是您在 created 钩子内初始化了 Container 对象,这使得它没有反应。如果你想让一个状态响应,你需要将它初始化为初始数据(或使用Vue.observable API)。我对 TS 了解不多,但看看the docs,我认为你需要做的就是......

@Component( components:   Basic, , )
export default class Form extends Vue 
    Container: object = 
        name: null
    

【讨论】:

确实如此!我的印象是,通过在类中简单地声明Container,它将获得undefined 的初始值,这足以启用反应性。当我实施您建议的更改(或最初将 Container 设置为 null 时,反应性已启用。【参考方案2】:

由于 Vue 不允许我们改变 props,我们需要创建 Container 的本地副本,所以让我们为其使用 computed 属性。当我们尝试访问这个属性时,我们得到了我们的Component,但是当我们想要设置它时,我们只是触发了我们的父组件的事件,新值为Component,我们将通过props获得。

Reference

<Basic v-model="container" />

// Basic.vue
<input v-model="localContainer.name">

props: 
  value: 
    type: Object,
    required: true
  
,
computed: 
  localContainer: 
    get() 
      return this.value
    ,
    set(container) 
      this.$emit('input', container)
    
  

【讨论】:

以上是关于在 Vue 中将对象属性与子组件同步的主要内容,如果未能解决你的问题,请参考以下文章

Vue 父组件与子组件之间传值

vue学习之父组件与子组件之间的交互

vue父组件与子组件之间的传值

Vue 组件间进行通信

vue~父组件与子组件的交互

vue父组件与子组件生命周期钩子顺序是啥?