【问题标题】:Minification of Vuejs App causes browser to crash缩小 Vuejs App 导致浏览器崩溃
【发布时间】:2018-12-07 19:23:53
【问题描述】:

我想确保这个 Vue 应用程序的用户只在输入框中输入格式正确的货币值。为此,我添加了一个正则表达式测试,以确保用户只能输入格式正确的值。根据我在网上为 JavaScript 找到的一堆不同的 Regex 测试人员,Regex 可以正常工作。当我在开发环境中运行应用程序时,一切正常。

但是,当我使用npm run build 并使用应用程序的缩小版时,在输入框中输入非数字会导致网络浏览器崩溃。 Windows 任务管理器显示该特定选项卡的 CPU 使用率急剧上升。使用 Chrome 调试器时,看起来任何非数字字符都会导致应用程序进入无限循环。但是,在非缩小版本中不会发生这种情况。

要重现问题,请使用 Vue CLI 创建一个新的 Vue 项目。编辑 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-filters,因此请使用 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 文件(确保将其放在 components 文件夹下名为 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 运行缩小的代码。在应用中输入非数字字符会导致浏览器崩溃。

此代码的缩小版本是否会导致无限循环?

【问题讨论】:

  • 我很难重现该问题,因为您共享的代码并不是真正独立的。 (见minimal reproducible example)。您能否测试以下内容:如果您删除 ` else if (/^\d*(\.\d{1,2})?$/g.test(clazz.payroll)) {` 条件,构建版本是否仍然有你提到的错误?如果从premiumDue(clazz) 返回的内容中删除currency 过滤器,错误是否仍然存在?这里没有引起我的注意,但也许你在依赖它的函数中修改clazz,导致无限更新循环?
  • 我添加了一些额外的信息,可以让您重新创建错误。

标签: javascript vue.js minify


【解决方案1】:

当您在模板中的某处引用error 时,问题开始出现。 Vue 开发服务器将开始警告您“您可能在组件渲染函数中有无限更新循环。”。这可能是导致您构建的版本崩溃的原因。

'You may have an infinite update loop in a component render function.' 是什么意思?什么意思?

当模板中的数据发生变化时,Vue 会重新渲染带有数据的模板。没有什么奇怪的事情发生。如果您引用变量 numberOfUnicorns 并添加 1,因为您发现了一个`,您希望它反映在屏幕上。

无限更新循环意味着在渲染期间以某种方式更改了在渲染期间使用的变量。这通常是由不“纯”的函数引起的(Wikipedia)

为什么会发生在你身上?

您的方法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 上创建一个触发 validate 函数的观察者来模拟这一点。

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

一种稍微不那么激进的验证方法是在您的输入上使用blur 事件来触发验证。你不会使用观察者。这样错误只会在用户完成输入时弹出。

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

【讨论】:

    猜你喜欢
    • 2014-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-09
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多