9 Vue性能技巧

本文有九个技巧,介绍如何提高Vue应用程序的性能,提高显示速度并减小捆绑包的大小。

使用组件


功能组件


假设我们有一个简单的小组件。 他所做的只是根据传递的值显示一个特定的标签:

<template> <div> <div v-if="value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script> 

可以通过添加功能属性来优化此组件。 功能组件编译为简单函数,并且没有局部状态。 因此,它的性能要高得多:

 <template functional> <div> <div v-if="props.value"></div> <section v-else></section> </div> </template> <script> export default { props: ['value'] } </script> 

Vue v3.0中的功能组件外观示例

分离为子组件


想象一下要显示执行某些复杂任务所需的组件:

 <template> <div :style="{ opacity: number / 300 }"> <div>{{ heavy() }}</div> </div> </template> <script> export default { props: ['number'], methods: { heavy () { /*   */ } } } </script> 

这里的问题是,Vue每次重新渲染组件时(即,每次props值更改时)都将执行heavy()方法。

如果将重型方法分为子组件,则可以轻松优化此类组件:

 <template> <div :style="{ opacity: number / 300 }"> <ChildComp/> </div> </template> <script> export default { props: ['number'], components: { ChildComp: { methods: { heavy () { /*   */ } }, render (h) { return h('div', this.heavy()) } } } } </script> 

怎么了 Vue自动跳过未更改相关数据的组件的呈现。 因此,当在父组件中更改props时,将重新使用子组件,并且不会重新启动heavy()方法。

请注意,这仅在子组件在父组件中不具有数据依赖性的情况下才有意义。 否则,将与父代一起重新创建子代,然后进行这种优化是没有意义的。

Getter本地缓存


以下组件具有基于第二个计算属性的某种计算属性:

 <template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { let result = this.start for (let i = 0; i < 1000; i++) { result += this.base } return result } } } </script> 

这里重要的是,基属性在循环中被调用,这导致了复杂性。 每次访问反应性数据时,Vue都会运行一些逻辑来确定如何以及如何访问哪些数据以建立依赖关系等等。 如我们的示例所示,如果有大量的呼叫,那么将汇总这些小的开销。

要解决此问题,只需访问一次base并将值保存到局部变量中即可:

 <template> <div :style="{ opacity: start / 300 }">{{ result }}</div> </template> <script> export default { props: ['start'], computed: { base () { return 42 }, result () { const base = this.base // <-- let result = this.start for (let i = 0; i < 1000; i++) { result += base } return result } } } </script> 

在v-show中重用DOM


看下面的例子:

 <template functional> <div> <div v-if="props.value"> <Heavy :n="10000"/> </div> <section v-else> <Heavy :n="10000"/> </section> </div> </template> 

这里,我们有一个包装器组件,该组件使用v-if和v-else来切换某些子组件。

了解v-if的工作原理很重要。 每次切换状态时,一个子组件将被完全销毁(调用destroy()钩子,将从DOM中删除所有节点),第二个子组件将被完全创建并再次安装。 而且,如果这些组件“很重”,那么在切换时您将挂起一个接口。

更具生产力的解决方案-使用v-show

 <template functional> <div> <div v-show="props.value"> <Heavy :n="10000"/> </div> <section v-show="!props.value"> <Heavy :n="10000"/> </section> </div> </template> 

在这种情况下, 两个子组件将立即创建并安装,并同时存在。 因此,Vue在切换时不需要破坏和创建组件。 他所做的只是隐藏其中一个,并使用CSS显示第二个。 因此,切换状态会更快,但是您应该了解这将导致高昂的内存成本。

使用<keep-alive>


因此,一个简单的组件是路由器上的包装器组件。

 <template> <div id="app"> <router-view/> </div> </template> 

问题与前面的示例相似-路由器之间的所有组件都将在路由之间的转换期间创建,安装和销毁。

而且这里的解决方案很相似-告诉Vue不要破坏而是要缓存和重用组件。 您可以使用特殊的内置<keep-alive>组件执行此操作:

 <template> <div id="app"> <keep-alive> <router-view/> </keep-alive> </div> </template> 

这种优化将导致内存消耗增加,因为Vue将支持更多“存活”的组件。 因此,不要在所有路线上都使用这种方法,尤其是当它们很多时。 您可以使用include和exclude属性来设置规则,哪些路由应该被缓存,哪些不应该:

 <template> <div id="app"> <keep-alive include="home,some-popular-page"> <router-view/> </keep-alive> </div> </template> 

延迟渲染


以下示例是一个包含多个非常繁重的子组件的组件:

 <template> <div> <h3>I'm an heavy page</h3> <Heavy v-for="n in 10" :key="n"/> <Heavy class="super-heavy" :n="9999999"/> </div> </template> 

问题在于,组件处理是主线程中的同步操作。 在此示例中,浏览器将不会显示任何内容,直到所有父级和子级组件的处理完成。 而且,如果要花费大量时间来处理子公司,那么您将在界面或空白屏幕中滞后一段时间。

您可以通过延迟子组件的渲染来改善这种情况:

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="defer(2)"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="defer(3)" class="super-heavy" :n="9999999"/> </div> </template> <script> import Defer from '@/mixins/Defer' export default { mixins: [ Defer() ] } </script> 

在这里,defer(n)函数返回错误的n帧。 之后,它总是返回true。 它用于将模板的一部分处理推迟几帧,从而使浏览器可以绘制界面。

如何运作? 如我上面所述,如果v-if指令中的条件为false,则Vue会完全忽略模板的一部分。

在动画的第一帧,我们得到:

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="false"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template> 

Vue只是简单地安装了标题,然后浏览器显示它并请求第二帧。 此时,defer(2)将开始返回true

 <template> <div> <h3>I'm an heavy page</h3> <template v-if="true"> <Heavy v-for="n in 10" :key="n"/> </template> <Heavy v-if="false" class="super-heavy" :n="9999999"/> </div> </template> 

Vue将创建10个子组件并将其安装。 浏览器将显示它们并请求下一帧,在此下一帧defer(3)将返回true。

因此,我们创建了一个逐渐处理的沉重组件,使浏览器能够显示模板中已安装的部分,这将大大改善UX,因为该界面看起来不会像挂起一样。

混音器延迟代码:

 export default function (count = 10) { return { data () { return { displayPriority: 0 } }, mounted () { this.runDisplayPriority() }, methods: { runDisplayPriority () { const step = () => { requestAnimationFrame(() => { this.displayPriority++ if (this.displayPriority < count) { step() } }) } step() }, defer (priority) { return this.displayPriority >= priority } } } } 

惰性组件加载


现在让我们来谈谈导入子组件:

 import Heavy from 'Heavy.js' export default { components: { Heavy } } 

在传统导入中,子组件在浏览器到达import语句时立即加载。 或者,如果您使用收集器,则您的子组件将包含在常规捆绑包中。

但是,如果您的子组件很大,则可以异步加载它。
这很容易做到:

 const Heavy = () => import('Heavy.js') export default { components: { Heavy } } 

这就是您所需要的 。 Vue可以立即使用延迟加载组件。 他将在需要时自己下载该组件,并在准备好后立即显示。 您可以使用此方法在任何地方进行延迟加载。

如果使用收集器,那么有关Heavy.js的所有内容都将在一个单独的文件中删除。 因此,您将减少初始下载期间文件的重量,并提高显示速度。

延迟加载视图


对于用于路由的组件,延迟加载非常有用。 由于您不需要同时下载路由的所有组件,因此建议您始终使用以下方法:

 const Home = () => import('Home.js') const About = () => import('About.js') const Contacts = () => import('Contacts.js') new VueRouter({ routes: [ { path: '/', component: Home }, { path: '/about', component: About }, { path: '/contacts', component: Contacts }, ] }) 

仅当用户首次请求指定的路由时,才会加载这些组件中的每个组件。 而不是之前。

动态组件


同样,您可以轻松地对动态组件使用延迟加载:

 <template> <div> <component :is="componentToShow"/> </div> </template> <script> export default { props: ['value'], computed: { componentToShow() { if (this.value) { return () => import('TrueComponent.js') } return () => import('FalseComponent.js') } } } </script> 

同样,每个组件仅在需要时加载。

与Vuex合作


避免大笔钱


假设您需要在存储中存储大量数据:

 fetchItems ({ commit }, { items }) { commit('clearItems') commit('addItems', items) //   10000  } 

问题是这样的。 所有提交都是同步操作。 这意味着处理大型数组将在整个工作期间阻塞您的接口。

要解决此问题,您可以将数组拆分为多个部分,然后一次添加一个,这样浏览器就有时间绘制新框架了:

 fetchItems ({ commit }, { items, splitCount }) { commit('clearItems') const queue = new JobQueue() splitArray(items, splitCount).forEach( chunk => queue.addJob(done => { //       requestAnimationFrame(() => { commit('addItems', chunk) done() }) }) ) //      await queue.start() } 

您将数据添加到存储中的参与者越小,界面将越平滑,完成任务的总时间就越长。

此外,使用这种方法,您可以为用户显示下载指示符。 这也将大大改善他的应用程序体验。

在不需要的地方关闭反应性


还有今天的最后一个例子。 我们有一个类似的任务:我们向存储中添加一个非常大的对象数组,并带有一系列嵌套级别:

 const data = items.map( item => ({ id: uid++, data: item, // <--    vote: 0 }) ) 

事实是,Vue将递归遍历所有嵌套字段并使它们具有反应性,如果您有大量数据,这可能会很昂贵,但更重要的是-不必要。

如果您的应用程序以仅依赖于顶级对象的方式构建,并且不引用以下几个级别的反应性数据,则可以关闭反应性,从而从大量的额外工作中节省了Vue:

 const data = items.map( item => optimizeItem(item) ) function optimizeItem (item) { const itemData = { id: uid++, vote: 0 } Object.defineProperty(itemData, 'data', { //    "-" configurable: false, value: item }) return itemData } 

Source: https://habr.com/ru/post/zh-CN483042/


All Articles