【问题标题】:Updating nested v-model with computed propertes instead of watcher in VueJS 2在 VueJS 2 中使用计算属性而不是观察者更新嵌套的 v-model
【发布时间】:2021-11-10 06:47:24
【问题描述】:

鉴于以下 VueJS 中的示例和简化组件:

MyForm(父组件)

<template lang="pug">
  user-component(v-model="apiData.user")
</template>

<script>
export default {
  name: "MyForm",
  components: {
    UserComponent
  },
  data() {
    return {
      apiData: {},
    };
  },
  watch: {
    "apiData.user.stateless": function (value) {
      if (value) {
        this.apiData.user.nationality = null;
      }
    },
  async mounted() {
    const response = await ApiService.getData();
    this.apiData = response.data;   
  },
};
</script>

UserCompoent(子组件)

<template lang="pug">
  b-form-group(label="Nationality")
    b-form-input(:value="value.nationality")
  b-form-checkbox(:checked="value.stateless") Stateless
</template>

<script>
export default {
  name: "UserCompoent",
  props: {
    value: {
      type: Object,
      required: true
    }
  },
};
</script>

如您所见,我正在使用嵌套属性apiData.user.stateless 的观察程序来更新v-model 属性apiData.user.nationality。阅读文档后,似乎我应该更喜欢计算属性而不是使用 watch

但是在这个例子中,我将如何使用计算属性而不是观察者呢?据我所知,计算属性不能对嵌套值进行操作?

【问题讨论】:

    标签: vue.js vuejs2


    【解决方案1】:

    确实,只有在没有其他解决方案的情况下才应该使用观察者,因为它可能会导致副作用并使调试变得一团糟。在您的情况下,可能没有必要。

    计算属性从不用于直接更新值。所以这也不是一个选择。您应该尽可能使用它们,但仅用于将值转换为不同的格式,作为方法或其他计算属性或模板中的辅助值。

    一般来说,您应该考虑您的组件架构。这里的问题是您使用对象作为 v-model 值,这也可能导致副作用。当然你可以这样做,但是维护起来更复杂,你必须正确地实现它。假设您有多个组件将此对象用作v-model 值。在最坏的情况下,组件会覆盖它们不应该覆盖的值。为了防止这种情况,您通常只使用单个原始值作为v-model,而不是对象。

    实际上,您实现它的方式是偶然的。在 Vue 2 中,v-model 要求您从子组件向父组件发出 input 事件,因此值会在父组件中更新(查看此处了解更多详细信息:https://vuejs.org/v2/guide/components.html#Using-v-model-on-Components)。在您的情况下,它可以工作,因为您的 v-model 是一个对象 - 意味着对该对象的引用被传递给子组件,并且您正在更新此对象引用的属性。

    所以,让我们稍微重构一下您的代码(未经测试,但概念应该很清楚):

    1.假设您在子组件中只有少数值要更新

    在您的情况下,v-model 不是一个选项,因为您有 2 个模型值要在同一个组件中更新。 Vue 3 会支持它 (https://v3.vuejs.org/guide/component-custom-events.html#multiple-v-model-bindings),但是当您使用 Vue 2 时,我建议您手动完成:

    <template>
      <user-component
        :nationality="apiData.user.nationality"
        :stateless="apiData.user.stateless"
        @update:nationality="onUpdateNationality"
        @update:stateless="onUpdateStateless"
      />
    </template>
    
    <script>
    export default {
      name: "MyForm",
      components: {
        UserComponent
      },
      data() {
        return {
          apiData: {},
        };
      },
      async mounted() {
        const response = await ApiService.getData();
        this.apiData = response.data;   
      },
    
      methods: {
        onUpdateNationality (value) {
          this.apiData.user.nationality = value
        },
    
        onUpdateStateless (value) {
          this.apiData.user.stateless = value
    
          if (value) {
            this.apiData.user.nationality = null // You also could handle it in your child component
          }
        }
      }
    };
    </script>
    
    <template>
      <b-form-group
        label="Nationality"
      >
        <b-form-input
          :value="nationality"
          @input="onUpdateNationality($event.target.value)"
        />
        <b-form-checkbox
          :checked="stateless"
          @click="onClickStateless"
        >
          Stateless
        </b-form-checkbox>
      </b-form-group>
    </template>
    
    <script>
    export default {
      name: "UserCompoent",
      props: {
        nationality: {
          type: String,
          required: true
        },
    
        stateless: {
          type: Boolean,
          required: true
        }
      },
    
      methods: {
        onClickStateless () {
          this.$emit('update:stateless', !this.stateless)
        },
    
        onUpdateNationality (nationality) {
          this.$emit('update:nationality', nationality)
        }
      }
    };
    </script>
    

    2。假设您的子组件更新了许多道具

    <template>
      <user-component
        :user="apiData.user"
        @update:user="onUpdateUser"
      />
    </template>
    
    <script>
    export default {
      name: "MyForm",
      components: {
        UserComponent
      },
      data() {
        return {
          apiData: {},
        };
      },
      async mounted() {
        const response = await ApiService.getData();
        this.apiData = response.data;   
      },
    
      methods: {
        onUpdateUser ({ nationality, stateless }) {
          this.apiData.user.nationality = nationality
          this.apiData.user.stateless = stateless
    
          if (stateless) {
            this.apiData.user.nationality = null // You also could handle it in your child component
          }
        }
      }
    };
    </script>
    
    <template>
      <b-form-group
        label="Nationality"
      >
        <b-form-input
          :value="form.nationality"
          @input="onUpdateNationality($event.target.value)"
        />
        <b-form-checkbox
          :checked="form.stateless"
          @click="onClickStateless"
        >
          Stateless
        </b-form-checkbox>
      </b-form-group>
    </template>
    
    <script>
    export default {
      name: "UserCompoent",
      props: {
        user: {
          type: Object,
          requird: true
        }
      },
    
      data () {
        return {
          form: {
            nationality: this.user.nationality,
            stateless: this.user.stateless
          }
        }
      }
    
      methods: {
        onClickStateless () {
          this.form.stateless = !this.form.stateless
          this.$emit('update:user', this.form)
        },
    
        onUpdateNationality (nationality) {
          this.form.nationality = nationality
          this.$emit('update:user', this.form)
        }
      }
    };
    </script>
    

    【讨论】:

    • 我用子组件的示例更新了原始帖子。也许这会有所帮助。我基本上要做的是:如果用户选中stateless复选框,则nationality输入字段的内容应设置为null
    • 好的,我已经根据你的描述更新了答案
    • 谢谢,我明白你的意思了。这听起来很合理。但是,还有一件事我不太明白。假设子组件不仅有两个属性,而且有二十个。这是否意味着我需要定义 20 个单个道具并从父组件传递它们?这不会使代码混乱吗?
    • 没错。我也有这样的组件。因此,您可以使用不同的方法(我已将其添加到我的答案中)。传递巨大的道具,复制子组件中的值并仅在您的子组件内进行更新。如果有任何变化,请将更新发送到父组件,以便您可以更新原始对象。
    • 太好了,我也是这么想的。这种方法似乎有效。
    【解决方案2】:

    您可以让计算属性直接与用户属性有关。

    然后使用get & set:

    https://vuejs.org/v2/guide/computed.html#Computed-Setter


    [编辑]

    针对 OP 在 cmets 中提出的问题:

    实际上 - 既然您已经用更多细节更新了您的问题,我认为计算属性根本不是答案。

    @tho-masn 接受的答案显然是要走的路。

    带有 getter 和 setter 的计算属性是为某些东西创建 v-model 的好方法,即使某些东西是经过计算的。例如,我将它与 vuex 结合使用。

    但这不是你正在做的。您想在属性 B 更改时更新属性 A。这是一个事件,而不是计算属性。

    如果你还没有提出问题,我会删除我的答案。

    [/编辑]

    【讨论】:

    • 你介意详细说明一下吗,也许可以举个例子?
    猜你喜欢
    • 2021-11-29
    • 2017-09-02
    • 1970-01-01
    • 2017-06-15
    • 2016-04-22
    • 2015-05-26
    • 2021-10-20
    • 2018-01-02
    • 2020-09-09
    相关资源
    最近更新 更多