Минификация приложения Vuejs приводит к сбою браузера - PullRequest
0 голосов
/ 07 декабря 2018

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

Однако, когда я использую npm run build и использую минимизированную версию приложения, при вводе номера в поле ввода появляется веббраузер вылетает.Диспетчер задач Windows довольно резко показывает использование ЦП этой конкретной вкладки.При использовании отладчика Chrome, похоже, что любой нечисловой символ приводит к тому, что приложение входит в бесконечный цикл.Но этого не происходит в неминифицированной версии.

Чтобы воссоздать проблему, создайте новый проект Vue, используя Vue CLI.Отредактируйте файл App.vue, чтобы он выглядел так:

<template>
  <div id="app">
    <Reporting/>
  </div>
</template>

<script>
import Reporting from './components/Reporting'

export default {
  name: 'app',
  components: {
    Reporting
  }
}
</script>

<style>
#app {
  font-family: 'Avenir', Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

Отредактируйте файл main.js так, чтобы он выглядел следующим образом:

import Vue from 'vue'
import Vue2Filters from 'vue2-filters'

import App from './App.vue'

Vue.config.productionTip = false

Vue.use(Vue2Filters)

new Vue({
  render: h => h(App),
}).$mount('#app')

Вам необходимо установить фильтры vue2, поэтому установите его, используя npm install --save vue2-filters.

Добавьте этот компонент Reporting.vue:

<template>
    <div id="Reporting" style="min-height: inherit; display: flex; flex-direction: column;">
        <div class="mainbody">
            <div id="content">
                <ErrorList v-if="error" :error="error"/>
                <table>
                    <thead>
                        <tr>
                            <th scope="col">
                                State
                            </th>
                            <th scope="col">
                                Class
                            </th>
                            <th scope="col">
                                Description
                            </th>
                            <th scope="col">
                                Net Rate
                            </th>
                            <th scope="col">
                                Payroll
                            </th>
                            <th scope="col">
                                Premium Due
                            </th>
                        </tr>
                    </thead>
                    <tbody>
                        <tr v-for="(clazz, index) in classes" :key="index">
                            <td scope="row" data-label="State">
                                {{ clazz.state }}
                            </td>
                            <td data-label="Class">
                                {{ clazz.classCode }}
                            </td>
                            <td data-label="Description">
                                {{ clazz.description }}
                            </td>
                            <td data-label="Net Rate" class="alignright">
                                {{ clazz.netRate | currency }}
                            </td>
                            <td data-label="Payroll">
                                <input type="text" v-model="clazz.payroll"/>
                            </td>
                            <td data-label="Premium Due" class="alignright">
                                {{ premiumDue(clazz) | currency }}
                            </td>
                        </tr>
                    </tbody>
                    <tfoot>
                        <tr class="subtotal">
                            <td colspan="3" aria-hidden="true"></td>
                            <td colspan="2" class="alignright lighter">Premium Total:</td>
                            <td class="alignright">{{ premiumTotal() | currency }}</td>
                        </tr>
                        <tr class="subtotal last">
                            <td colspan="3" aria-hidden="true"></td>
                            <td colspan="2" class="alignright lighter">Tax Total:</td>
                            <td class="alignright">{{ taxTotal() | currency }}</td>
                        </tr>
                        <tr class="grandtotal">
                            <td colspan="3" aria-hidden="true"></td>
                            <td colspan="2" class="alignright lighter">Grand Total:</td>
                            <td class="alignright">{{ (taxTotal() + premiumTotal()) | currency }}</td>
                        </tr>
                        <tr class="formbuttons">
                            <td colspan="4" aria-hidden="true"></td>
                            <td><button class="button-sm purple" @click="onClear">Clear</button></td>
                            <td><button class="button-sm purple" @click="onSubmit">Submit</button></td>
                        </tr>
                    </tfoot>
                </table>
            </div>
        </div>
    </div>
</template>

<script>
/* eslint-disable vue/no-unused-components */

import ErrorList from './shared/ErrorList'

export default {
    name: 'Reporting',
    components: { ErrorList },
    data() {
        return {
            error: null,
            classes: []
        }
    },
    methods: {
        onClear() {
            this.classes.forEach(clazz => {
                clazz.payroll = ''
            })
        },
        validate(lines) {
            for (let line of lines) {
                if (!(/^\d*(\.\d{1,2})?$/.test(line.quantity))) {
                    this.error = { message: 'Payroll must be in number format with no more than 2 places after decimal.' }
                    return false
                }
            }
            this.error = null
            return true
        },
        onSubmit(e) {
            let lines = []
            this.classes.forEach(clazz => {
                lines.push({
                    classCode: clazz.id,
                    quantity: clazz.payroll,
                    rate: clazz.netRate,
                    taxRate: clazz.taxRate
                })
            })
            this.validate(lines)
        },
        premiumDue(clazz){
            if (!clazz.payroll) {
                this.error = null
                return 0
            } else if (/^\d*(\.\d{1,2})?$/.test(clazz.payroll)) {
                this.error = null
                return (clazz.payroll / 100) * clazz.netRate
            } else {
                this.error = { message: 'Payroll must be in number format with no more than 2 places after decimal.' }
                return 0
            }
        },
        premiumTotal() {
            return this.classes.reduce((accumulator, clazz) => {
                return (clazz.payroll) ? accumulator + this.premiumDue(clazz) : accumulator + 0
            }, 0)
        },
        taxDue(clazz){
            return this.premiumDue(clazz) * clazz.taxRate
        },
        taxTotal() {
            return this.classes.reduce((accumulator, clazz) => {
                return (clazz.payroll) ? accumulator + this.taxDue(clazz) : accumulator + 0
            }, 0)
        },
        initialize() {
            this.classes.push({
                classCode: "5540",
                description: "Roofing",
                name: "CC-00002",
                netRate: 12.34,
                state: "CA",
                taxRate: 0.035
            })
            this.classes.push({
                classCode: "8810",
                description: "Clerical",
                name: "CC-00001",
                netRate: 0.68,
                state: "CA",
                taxRate: 0.035
            })
        }
    },
    beforeRouteUpdate(to) {
        this.onClear()
        this.initialize()
    },
    created() {
        this.initialize()
    }
}
</script>

<style scoped>
</style>

Добавьте этот файл ErrorList.vue (обязательно поместите его в подпапку с именем shared в папке компонентов):

<template>
    <section>
        <div v-if="error.message">{{ error.message }}</div>
        <div v-if="error.errors && error.errors.length > 0">
            <ul>
                <li v-for="(err, index) in error.errors" :key="index"><h1>{{ err.message }}</h1></li>
            </ul>
        </div>
    </section>
</template>

<script>
  export default {
    name: 'ErrorList',
    props: ['error']
  }
</script>

<style scoped>

</style>

Теперь запустите команду npm run build.Затем выполните команду serve -s dist, чтобы запустить минимизированный код.В приложении введите нечисловые символы во входные данные, и это приведет к сбою браузера.

Есть ли какая-либо причина, по которой минимизированная версия этого кода может вызвать бесконечный цикл?

1 Ответ

0 голосов
/ 12 декабря 2018

Проблема начинает возникать, когда у вас есть ссылка на error где-то в вашем шаблоне.Сервер Vue dev начнет предупреждать вас о том, что «у вас может быть бесконечный цикл обновления в функции рендеринга компонента».Это, вероятно, то, что приводит к сбою вашей встроенной версии.

Что означает «У вас может быть бесконечный цикл обновления в функции рендеринга компонента».значит?

Vue повторно отображает шаблон с данными, когда данные в нем изменяются.Ничего странного там не происходит.Если вы ссылаетесь на переменную numberOfUnicorns и добавляете 1, потому что вы заметили one`, вы хотите, чтобы это отразилось на экране.

Бесконечный цикл обновления означает, что каким-то образом при рендеринге переменной, используемой во время рендеринга,измененоЭто обычно вызывается функциями, которые не являются «чистыми» ( Википедия ) .

Почему это происходит для вас?

Ваш метод premiumDue комплектов this.error.Как я упоминал ранее, проблема начинает возникать, когда в шаблоне используется error.this.error передается в ErrorList в вашем случае, а затем вызывается premiumDue, который устанавливает this.error и помечает визуализированный вид как грязный.Затем представление перерисовывается.Над.И более.И снова.

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

Как вы решаетеэто?

Это сложная часть.Сначала вам нужно переписать premiumDue, чтобы оно было чистым.

premiumDue(clazz) {
  if (!clazz.payroll) {
    return 0;
  } else if (/^\d*(\.\d{1,2})?$/.test(clazz.payroll)) {
    return (clazz.payroll / 100) * clazz.netRate;
  } else {
    return 0;
  }
}

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

validatePartial() {
  for (let line of this.classes) {
    if (line.payroll && !/^\d*(\.\d{1,2})?$/.test(line.payroll)) {
      this.error = {
        message:
          "Payroll must be in number format with no more than 2 places after decimal."
      };
      return false;
    }
  }
  this.error = null;
  return true;
}

В основном это то же самое, что и validate, но вместо циклического перебора аргумента мы используем this.classes.Сообщение об ошибке выдается только в том случае, если в line.payroll действительно есть что-то.

Нам все еще нужно активировать его, и я вижу два варианта для этого.Раньше он срабатывал при каждом нажатии клавиши, потому что каждое нажатие клавиши менялось this.classes, что вызывало повторный рендеринг.Мы можем эмулировать это, создав наблюдатель на this.classes, который запускает функцию проверки.

watch: {
  classes: {
    deep: true,
    handler() {
      this.validatePartial();
    }
  }
}

Несколько менее агрессивный способ проверки состоит в использовании события blur на ваших входах для запуска проверки.вместо.Вы бы не использовали наблюдателя.Таким образом, ошибка появляется только после того, как пользователь закончил печатать.

<input type="text" v-model="clazz.payroll" @blur="validatePartial" />

Edit Vue Template

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...