Как правильно организовать код большой формы-калькулятора на vue.js?

Создаю свою первую форму на vue.js и видимо делаю, что-то не так. Форма достаточно большая. Когда количество строк кода перевалило за 200 я решил, что нужно разбить форму на компоненты поблочно. В результате потребовалось учесть много нюансов и в результате код не упростился, а усложнился.

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

Подробности
Чтобы в data() была понятная структура полей, я выделил каждый блок в отдельный объект. Итак, в каждый компонент я пробрасываю этот объект в виде prop и с его помощью инициализирую поля формы. Сразу возникают 2 проблемы:

1. Оказывается, редактировать props не рекомендуется. Но ведь я только за этим и передал props, чтобы пользователь своим вводом их редактировал.
2. Vuelidate быстро настроивается, если есть v-model, а без неё потребуются сеттеры.

Решено. Нужно инициализировать data из props. Как это сделать? Одним свойством-объектом? Тогда потребуется в полях формы прописывать v-model как свойство объекта. Но я же хотел лаконичный код. Тогда придется в data заводить несколько свойств и присваивать каждому свойство из props. Наверно можно повесить на хук created инициализацию data (вызвать в цикле по пропсу set()). Но тогда я не увижу в компоненте структуру data(). Это плохо.

Допустим, я проинициализировал data статично свойствами из пропса. Теперь могу привязать data к полям формы и изменять. Но! Новые значения нужно пробрасывать обратно к родителю. Там несколько свойств и на каждое придется повесить emit.

Но это ещё не всё. В каждом дочернем блоке будет валидация полей, результат которой тоже нужно будет передать в родителя. Тут вообще непонятно как поступить. Наверно нужно через emit отправлять объект $v или его свойства. В родительском компоненте из этих объектов будет собираться общий объект $v с доступом ко всем полям валидации. Не уверен, что такое вообзе возможно.

В итоге никакого упрощения структуры кода не получается. Валидацию вообще непонятно как организовать с разделением по компонентам. Также непонятно с ценами. Они обновляются только в родительском компоненте (забираются ajax запросом с сервера). Но при моей организации кода они не только не будут актуализироваться в дочерних компонентах, но и перезатрутся при первом обновлении полей формы. В итоге их придется вынести в отдельный объект, который будет дублировать структуру блоков.

Основной компонент формы: App.vue
<template>
  <div id="app">
    <h1>Калькулятор клининговых услуг</h1>

    <form class="calculatorForm">
        

        <Cleaning  v-bind:item="cleaning" @input="updateCleaning(item)" ></Cleaning>

        <button @click.prevent="getPriceInfo" >Рассчитать</button>
    </form>


  </div>
</template>

<script>

import Vue from 'vue'

import axios from 'axios'

import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)

import { required, minLength, between, numeric } from 'vuelidate/lib/validators'
import { helpers } from 'vuelidate/lib/validators'

const mustBeCool = (value) => !helpers.req(value) || value.indexOf('cool') >= 0



import Carpet from './components/Carpet.vue'
import Cleaning from './components/Cleaning.vue'

axios.defaults.baseURL = 'https://site.ru/calculator/php'

export default {
    name: 'app',
    data () {
      return {

        services: ["cleaning","windows", "carpets","furniture"],
        cleaning: {type: 2, area: null, showAbout: false, price: null, isActive: false},
        windows: {isClassic:true, oneFoldCount: 0, twoFoldCount: 0, threeFoldCount: 0, area: null, isActive: false}, 
        carpets: {items:[{type: 1, area: null}], isActive: false},

        endpoint: '/index.php'
      }
    },

    validations: {
        cleaning: {    
            area:{
                required
            }
        },

        windows: {
            oneFoldCount:{ 
                numeric 
            },
            twoFoldCount:{ 
                numeric 
            },
            threeFoldCount:{ 
                numeric }
        }
    },

    components: {Cleaning,Carpet},

    methods: {

        getPriceInfo: function(event) {
            
            // Здесь будет код отправки данных для расчёта на сервер и получения стоимости услуг
        },

        updateCleaning: function(item) {
            
            this.cleaning = item;
        },

        updateCarpet: function(item, index) {
            
            this.carpets.items[index] = item;
        },

        addCarpet: function() {
            
            this.carpets.items.push({type: 1, area: null});
        }
    }
}
</script>



Компонент одного из блоков формы: Cleaning.vue

<template>

        <div class="serviceBlock" v-bind:class="{ active: isActive }" >
            <div class="serviceSwitcher">
                <label class="serviceName" for="serviceCleaning">Уборка
                    <input type="checkbox" id="serviceCleaning" value="cleaning" @change="isActive = !isActive" ><span class="checkmark"></span>
                </label>
            </div>
            <div class="serviceContent" v-if="isActive">
                <div class="serviceFields" >
                    
                    <div class="fieldItem">
                        <label for="cleaningType">Тип уборки</label>
                        <select id="cleaningType" v-model="type">
                          <option disabled value="">Выберите один из вариантов</option>
                          <option value="1" >Быстрая</option>
                          <option value="2" >Генеральная</option>
                          <option value="3">После ремонта</option>
                        </select>
                    </div>

                    <div class="fieldItem">
                        <label for="cleaningArea">Площадь помещения (кв. м.)</label>
                        <input type="number" id="cleaningArea" min="1" v-model="area" v-on:input="filterArea">
                    </div>

                </div>

                <div class="servicePrice">
                        <div class="title">Стоимость уборки:</div>
                        <div class="value" v-if="price !== null" ><span class="price"></span><span class="currency">₽</span></div>
                        <div class="serviceErrors">
                            <div class="error" v-if="!$v.area.required">Введите площадь помещения, чтобы узнать стоимость уборки</div>
                        </div>
                </div>

                <div class="serviceDescription">
                    <a class="aboutLink" @click="showAbout = !showAbout" >Что входит в стоимость?</a>
                    <div class="aboutContent" v-if="showAbout" >Описание услуги</div>
                </div>
            </div>
        </div>

</template>

<script>


import Vue from 'vue'

import Vuelidate from 'vuelidate'
Vue.use(Vuelidate)

import { required} from 'vuelidate/lib/validators'



export default {

    name: 'Cleaning',

    props:['item'],

    data () {
            return {
                type: this.item.type, 
                area: this.item.area, 
                showAbout: this.item.showAbout, 
                price: this.item.price, 
                isActive: this.item.isActive
            }
    },

    updated() 
    {
        $this.$emit('updated', this.data());
    },

    methods: {
        
        validateArea: function()
        {
            // Натуральное число
            const templateArea = /^(?:[1-9]\d{0,4})$/;

            if(templateArea.test(this.area))
            {
                return true;
            }

            return false;
            
        },

        filterArea: function(event) {
            //Целое число или пустое значение
            const templateArea = /^(?:[1-9]\d{0,4}|)$/;
            if(templateArea.test(this.area))
            {
                this.previousArea = this.area;
            }
            else
            {
                this.area = this.previousArea;            
            }
        },
    }
}
</script>

  • Вопрос задан
  • 2257 просмотров
Пригласить эксперта
Ответы на вопрос 4
@Lavich
Frontend developer
Раздели свой компонент Cleaning.vue на Type.vue, Area.vue, Window.vue и т.п., в которых реализуй механику работы каждого элемента формы и его стилизацию; для них реализуешь работу через v-model:

<template>
                    <div class="fieldItem">
                        <label for="cleaningType">Тип уборки</label>
                        <select id="cleaningType" :value="value" @input="$emit('input', $event.value)">
                          <option disabled value="">Выберите один из вариантов</option>
                          <option value="1" >Быстрая</option>
                          <option value="2" >Генеральная</option>
                          <option value="3">После ремонта</option>
                        </select>
                    </div>
</template>


export default {
  props: {
    value: Number
}
}


в App.vue оставь валидацию и отправку на сервер:

<form class="calculatorForm">
        <Type v-model="type" />
        <Area v-model="area" />
        <Window v-model="windows" />
        ....
        <button @click.prevent="getPriceInfo" >Рассчитать</button>
    </form>
Ответ написан
vitaly_74
@vitaly_74
не знаком с vue. но код впринцепи понятен, попробуйте сделать так: все расчеты в одном файле, весь вывод в другом.
по сути у вас будет 3 файла. общий(собирающий)
так сказать "вид" который вставляет все необходимые данные.
и отдельный файл логики. как там у вас все расяитывается.
на каждую кнопку нет смысла создавать файл, ровно также как и нет смысла отрисовывать каждую кнопку в отделтном файле
Ответ написан
@Savva_Tobolsk
Начинающий python/js разработчик
Подключи библиотеку mobx и выноси всю логику из шаблонов, пускай шаблоны только передают данные в один из mobx классов, а сами классы пускай решают что и где отображать.
Ответ написан
Комментировать
Форма - это самый распространенный компонент веб-страниц. Сейчас очень много фреймворков и библиотек для решения стандартных задач. Например, легковесный фреймворк Buefy для Vue (если хочется, то css стили можно отключать, оставив только js начинку). Ваш код значительно уменьшится от применения готовых компонентов. Повторное использование ЧУЖОГО кода должно стать для вас привычкой.

Во-вторых, подключите eslint и следуйте его рекомендациям. У вас очень много отступов и мест, где код можно сделать компактнее. Например я пишу так:
if (var === true) {
  do_something()
} else {
  print_something()
}


Создаю свою первую форму на vue.js и видимо делаю, что-то не так.


Ну и здесь у вас лишняя запятая после слова "делаю")))
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы