Условный реквизит в Vue2


У меня есть случай использования, когда мне может понадобиться передать объект props в качестве реквизита дочернему компоненту.

Изначально у меня была форма и таблица, содержащиеся в компоненте. Эта форма будет принимать входные данные, асинхронный запрос будет выполнен, и таблица будет отображаться для пользователя, чтобы сделать выбор. Затем пользователь может нажать кнопку и скрыть таблицу, а форма будет выведена обратно, чтобы параметры можно было ввести повторно. Так как содержание формы зависело от государства последние параметры поиска его родителя все еще находились в форме.

Проблема возникла, когда я рефакторировал компонент, чтобы сделать как форму, так и таблицу субкомпонентами родительского элемента. Теперь форма будет $emit событие своему родителю, который выполнит асинхронную операцию и передаст результат в виде 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>

Компонент формы.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>
Проблема с этим заключалась в том, что при первоначальном создании компонента ему передается пустой объект (или, скорее, объект только с пустыми свойствами, я предполагаю, что они равны одному и тому же thing), что означает, что мои свойства данных инициализируются как undefined.

Затем я попытался использовать тернарный код для инициализации объекта данных со значениями в реквизитах или пустой строке, например:

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. В любом случае такой подход казался "халтурным".

Я действительно не знаю, куда идти дальше. Я мог бы зафиксировать данные формы в хранилище и получить их снова, если форма будет перемонтирована, но я не чувствую себя обязанным делать что-то, что должно только существовать, пока его родитель смонтирован-это правильный подход.

Может ли кто-нибудь направить меня к более Vue способу достижения этого?

1 6

1 ответ:

Я думаю, что вы должны быть в состоянии разобраться в этом, переместив код в FormComponent из данных в вычисляемые.
Вычисляемые свойства являются реактивными, поэтому, когда initialValues prop в конечном итоге получает некоторые данные, форма должна отражать их.
Объект.назначение будет работать, если initialValues всегда имеет структуру address1, address2 и т. д. (Даже если они изначально равны нулю). Если нет (то есть initialValues initially = {}, вам придется выполнить проверку свойств по свойствам.

<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 вверх через emit и применить их к родительским formValues.

Альтернатива, которая мне очень нравится, - это Vuex, где форма получает свое начальное значение из магазина и отправляет его обратно в магазин через действие, которое запускает асинхронную работу и обновляет магазин результатами. По сути, ваш метод displayResults становится действием, но вам не нужна асинхронность..ждать.

Vuex формализует шаблон потока данных, чтобы вам было проще рассуждайте об этом и отлаживайте вещи. По существу, вам не нужно беспокоиться о передаче данных в реквизит и через Эмит.

Еще одна мысль, вы можете избежать сброса значений FormComponent, используя v-show вместо v-if в FormContainer. Это должно сохранить форму div (и это состояние), но скрыть его. Тогда вам, возможно, вообще не нужно будет иметь значения formValues на parent.

<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>