【问题标题】:Conditional props in Vue2Vue2 中的条件道具
【发布时间】:2017-09-23 07:28:57
【问题描述】:

我有一个用例,我可能需要将对象props 作为道具传递给子组件。

最初,我在一个组件中包含一个表单和一个表格。此表单将接受输入,将执行异步请求并呈现表格以供用户进行选择。然后用户可以点击一个按钮并隐藏表格并恢复表单以便重新输入参数。由于表单的内容取决于其父级的状态,因此最后的搜索参数仍在表单中。

当我重构组件以使表单和表格成为父组件的子组件时,问题就出现了。现在表单会将$emit 事件传递给其父级,后者将执行异步操作并将结果作为props 传递给表。这工作得很好,但是当用户点击“返回表单”按钮时,表单被重新渲染,从而将其状态重置为初始值。

我尝试将表单的内容存储在父级中并将其作为props 传递回表单,但这导致了最初设置值的问题。我不想直接变异props,所以我尝试了这种方法:

FormContainer.vue

<template>
  <div v-if="formShown">
    <form-component :initialValues="formValues" @formSubmitted="displayResults"></form-component>
  </div>
  <div v-if="tableShown">
    <table-component :results="fetchedResults" @returnToForm="returnToForm"></table-component>
  </div>
</template>
<script>
  export default {
    data(){
      return{
        formShown: true,
        tableShown: false,
        formValues:{
          address1: '',
          address2: '',
          address3: '',
          country: ''
        },
        fetchedResults: []
      }
    },
    methods:{
     async displayResults(){
        this.fetchedResults = await someAsynchronousCall();
        this.formShown = false;
        this.tableShown = true;
     },
     returnToForm(){
       this.tableShown = false;
       this.formShown = true;
     }
    }
  }
</script>

FormComponent.vue

<template>
  <!--Some form fields here, bound to data(){}, ommitted for brevity-->
</template>
<script>
  export default{
    props:['initialValues'],
    data(){
      return{
        //Original structure
        /*selectedAddress:{
          address1: '',
          address2: '',
          address3: '',
          country: ''
        }*/
        selectedAddress: JSON.parse(JSON.stringify(this.initialValues)) //Object.assign({}, this.initialValues) //tried both of these for deep copy
      }
    }
  }
</script>

这样做的问题是,当组件最初创建时,它传递了一个空对象(或者更确切地说是一个只有空属性的对象,我认为它们相当于同一件事),这意味着我的数据属性正在获取初始化为undefined

然后我尝试使用三元组来用 props 中的值或空字符串初始化数据对象,如下所示:

data(){
  return{
    selectedAddress: {
      address1: this.initialValues.address1 ? this.initialValues.address1 :'',
      address2: this.initialValues.address2 ? this.initialValues.address2 :'',
      address3: this.initialValues.address3 ? this.initialValues.address3 :'',
      country:this.initialValues.country ? this.initialValues.country :''
    }

  }
},

但这会引发错误,指出selectedAddress 未在实例上定义,但在渲染期间被引用。我猜这意味着使用三元来初始化道具是错误的。

然后我尝试检查 mounted(){} 生命周期钩子中的道具并在那里设置数据属性,如下所示:

mounted(){
      if(!this.initialValues || _.isEmpty(this.initialValues)){
        Logger.info(`no props passed, no setup needed`);
        return;
      }
      if(this.initalValues.address1){
        Logger.info(`setting address 1`);
        this.selectedAddress.address1 = this.initalValues.address1;
      }
      if(this.initialValues.address2){
        Logger.info(`setting address 2`);
        this.selectedAddress.address2 = this.initalValues.address2;
      }
      if(this.initialValues.address3){
        Logger.info(`setting address 3`);
        this.selectedAddress.address3 = this.initalValues.address2;
      }
      if(this.initialValues.country){
        Logger.info(`setting country`);
        this.selectedAddress.country = this.initalValues.country;
      }
    }

这适用于表单的第一次运行,但在安装组件时this.initialValues 始终为undefined。我检查了组件状态,发现initialValues 确实存在,只是在mounted 生命周期挂钩期间不存在。无论如何,这种方法感觉很“hacky”。​​

我不确定从这里去哪里。如果重新安装表单,我可以将表单数据提交到商店并再次获取它,但我不觉得提交应该只在其父安装时才存在的东西是正确的方法。

谁能指导我采用更 Vue 的方式来实现这一目标?

【问题讨论】:

    标签: javascript vuejs2 vue-component


    【解决方案1】:

    我认为您应该能够通过将 FormComponent 中的代码从数据转换为计算来解决这个问题。
    计算属性是反应性的,因此当 initialValues 属性最终获得一些数据时,表单应该反映它。
    如果 initialValues 始终具有 address1、address2 等结构(即使它们最初为 null),Object.assign 将起作用。如果不是(即 initialValues 最初 = {},则必须逐个属性检查。

    <template>
      <!--Some form fields here, bound to data(){}, ommitted for brevity-->
      <div>selectedAddress.address1</div>
      <div>selectedAddress.address2</div>
    </template>
    <script>
      export default{
        props:['initialValues'],
        data(){
        },
        computed: {
          selectedAddress() {
            return Object.assign({}, this.initialValues)
          }
        }
      }
    </script>
    

    要保留下一轮的表单值,您需要将 selectedAddress 向上传递通过发射并将它们应用到父表单值。

    我非常喜欢的另一种选择是 Vuex,表单从存储中获取初始值并通过操作回传到存储,这会启动异步工作并使用结果更新存储。本质上,您的 displayResults 方法变成了操作,但您不需要 async..await。

    Vuex 将数据流模式形式化,因此您可以更轻松地对其进行推理和调试。本质上,您不必担心通过发射将数据传入道具和传出。

    另一个想法,您可以通过在 FormContainer 中使用 v-show 而不是 v-if 来避免重置 FormComponent 的值。那应该保留表单 div (和它的状态)但隐藏它。然后,您可能根本不需要在 parent 上有 formValues。

    <template>
      <div v-show="formShown">
        <form-component :initialValues="formValues" @formSubmitted="displayResults"></form-component>
      </div>
      <div v-show="tableShown">
        <table-component :results="fetchedResults" @returnToForm="returnToForm"></table-component>
      </div>
    </template>
    

    【讨论】:

    • 我目前正在使用 Vuex,我实现的解决方法是使用 Vuex。我的问题是,我必须确保记住在所有适当的时间发送正确的操作以从存储中删除数据,即当表单容器的父级从 DOM 中删除时,当用户重置表单时从表单中,当用户从其父级中重置表单时等。我可以看到这是一种在表单中具有不正确初始状态的简单方法。
    • 使用计算属性是否意味着如果 initalValues 的内容在窗体仍处于打开状态时在父级中发生更改,则窗体字段值本身会更改?我试图使用Object.assign() 来断开表单值和从父级传递的数据之间的链接,如果在computed(){} 中使用它还会是这种情况吗?
    • 我更喜欢只是隐藏表单的想法,它会降低复杂性,并且它不是一个庞大的组件,挂在 DOM 周围。我知道删除未使用的元素而不是隐藏它们通常是公认的做法,但在这种情况下,我可能会提出例外
    • 不想听起来厚脸皮,请重新评论您对 Vuex 的第一条评论 - 正是!无论您是否使用 Vuex,围绕数据更改的复杂性都存在,但是 Vuex 使一切变得更加明确,因为您有一个 clearForm 突变,并且可以看到它在何时何地被调用。使用 v-show 解决方案,感觉就像我们使状态依赖于某些 html 标记,如果布局发生变化,这些标记可能会在稍后被其他开发人员重构。
    猜你喜欢
    • 1970-01-01
    • 2018-08-10
    • 2018-05-01
    • 2022-01-05
    • 2017-11-11
    • 1970-01-01
    • 2018-03-05
    • 2018-12-07
    • 2018-11-23
    相关资源
    最近更新 更多