Vue3提升性能
在 Vue 3 中,为了提升性能,可以遵循以下编码习惯和优化策略
1.避免将 v-if 和 v-for 一起使用
当 v-if 和 v-for 一起使用时,每次渲染都会先评估 v-for,然后再进行 v-if 条件判断。这意味着即使 v-if 的条件不满足,v-for 仍然会遍历整个列表,从而造成不必要的性能开销。
解决方案
1.1在 v-for 外部使用 v-if
将 v-if 放在 v-for 的外部,这样可以避免不必要的遍历操作:
<template>
<div v-if="shouldShowList">
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
</div>
</template>
<script>
export default {
data() {
return {
shouldShowList: true,
items: [
{ id: 1, name: 'Item 1' },
{ id: 2, name: 'Item 2' }
]
};
}
};
</script>
1.2使用计算属性过滤数据
使用计算属性提前过滤数据,然后直接在模板中渲染,相当于v-if和v-for逻辑分离
<template>
<div v-for="item in filteredItems" :key="item.id">
{{ item.name }}
</div>
</template>
<script>
export default {
data() {
return {
showActiveItems: true,
items: [
{ id: 1, name: 'Item 1', active: true },
{ id: 2, name: 'Item 2', active: false }
]
};
},
computed: {
filteredItems() {
return this.items.filter(item => item.active === this.showActiveItems);
}
}
};
</script>
1.3使用 template 包装
如果 v-if 只是用于过滤 v-for 的一些元素,可以使用 template 元素进行包装:
<template>
<div v-for="item in items" :key="item.id">
<template v-if="item.shouldShow">
{{ item.name }}
</template>
</div>
</template>
<script>
export default {
data() {
return {
items: [
{ id: 1, name: 'Item 1', shouldShow: true },
{ id: 2, name: 'Item 2', shouldShow: false }
]
};
}
};
</script>
2.使用 setup 函数
setup 函数是 Vue 3 的 Composition API 的核心部分。它允许你更灵活地组织逻辑,并且在组件实例创建之前执行,从而提升性能。
3.避免不必要的响应式数据
- 仅为需要响应的数据使用 ref 或 reactive。对于不会改变的数据,使用普通变量。
- 对于静态内容,可以使用 v-once 指令,这样内容只会渲染一次,并且不会在数据变化时重新渲染。
- 减少"响应深度",当你只需要对对象的第一层属性进行响应式处理时,使用 shallowRef 和 shallowReactive,可以减少性能开销。
const shallowState = shallowReactive({ foo: 'bar' });
4.避免不必要的计算属性和侦听器
- 仅在需要时使用计算属性和侦听器,避免它们对性能的负面影响。
- 合理使用 watch 和 watchEffect,确保仅在必要时进行副作用处理。
5.擅于运用缓存技术
5.1 使用 keep-alive 缓存组件
对于频繁切换的动态组件,可以使用 keep-alive 进行缓存,避免重复创建和销毁组件实例。
<keep-alive>
<component :is="currentComponent"></component>
</keep-alive>
5.2 使用 v-memo 指令
v-memo 指令允许你缓存包含大量静态内容的渲染输出,仅在指定的依赖项变化时重新渲染。
<p v-memo="[dependency]">This will be cached based on dependency changes.</p>
5.3 teleport移动
在需要将内容移动到指定位置时,使用 teleport 提高性能和可维护性。
<teleport to="#modals">
<modal-component></modal-component>
</teleport>
6.懒加载
对于不常用的组件,可以使用动态导入和延迟加载来提升初始加载性能。
const LazyComponent = defineAsyncComponent(() => import('./LazyComponent.vue'));
7.节流防抖技术
尽量减少组件的重新渲染次数。对频繁变化的数据,使用节流(throttle)或防抖(debounce)技术来减少更新频率。
import { debounce } from 'lodash';
const handleInput = debounce((value) => {
// 处理输入
}, 300);
8.优化模板内容
- 使用事件委托来减少大量相同类型事件的监
- 减少模板的复杂度和嵌套层次。
- 使用 CSS 的 display: none 代替 v-if,当你只需要隐藏元素而不是完全移除它时。
- 合理组织和复用样式,减少样式冲突和冗余。
- 避免在模板中直接使用内联函数表达式,改为在方法中定义逻辑.
- 在渲染长列表时,使用 v-for 的 key 属性确保列表元素的唯一性和稳定性。同时,考虑使用虚拟滚动技术(如 vue-virtual-scroller)来优化性能。
虚拟滚动是一种优化长列表渲染的方法,它通过只渲染当前视窗内的元素,大大减少了 DOM 元素的数量,从而提升了性能。虚拟滚动技术在处理大型数据集时尤其有效,因为它避免了同时渲染大量不在视窗中的元素。灵活性较高,支持固定高度和动态高度的列表项,适应多种场景需求
//RecycleScroller
<template>
<div>
<RecycleScroller
:items="items"
:item-size="50"
key-field="id"
>
<template #default="{ item, index }">
<div class="item">
{{ item.name }}
</div>
</template>
</RecycleScroller>
</div>
</template>
<script>
import { RecycleScroller } from 'vue-virtual-scroller'
export default {
components: {
RecycleScroller
},
data() {
return {
items: Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}` }))
}
}
}
</script>
如果列表项的高度(或宽度)不固定,可以使用 DynamicScroller 和 DynamicScrollerItem 组件
// DynamicScroller, DynamicScrollerItem
<template>
<div>
<DynamicScroller
:items="items"
key-field="id"
>
<template #default="{ item, index }">
<DynamicScrollerItem :item="item">
<div :style="{ height: item.height + 'px' }" class="item">
{{ item.name }}
</div>
</DynamicScrollerItem>
</template>
</DynamicScroller>
</div>
</template>
<script>
import { DynamicScroller, DynamicScrollerItem } from 'vue-virtual-scroller'
export default {
components: {
DynamicScroller,
DynamicScrollerItem
},
data() {
return {
items: Array.from({ length: 10000 }, (_, i) => ({ id: i, name: `Item ${i}`, height: 50 + Math.random() * 100 }))
}
}
}
</script>
9.SPA首屏优化
通过减小入口文件、静态资源本地缓存、按需加载 UI 框架、压缩图片资源、避免组件重复打包、开启 GZip 压缩以及使用服务器端渲染,可以显著提升 SPA 应用的首屏加载性能。这些优化策略可以改善用户体验,降低页面加载时间。
- 减小入口文件积
代码拆分
通过 Webpack、Vite 等构建工具进行代码拆分,将应用的不同部分分离成更小的块,并按需加载。
// 使用动态导入 (Dynamic Imports)
const Home = () => import('./components/Home.vue');
Tree Shaking
确保只打包实际使用的代码,移除未使用的模块和函数
//#配置文件
// 确保构建工具支持 Tree Shaking,如 Webpack 的 mode 设置为 production
module.exports = {
mode: 'production'
};
2.静态资源本地缓存
配置缓存策略
通过 HTTP 头配置缓存策略,减少不必要的资源请求。
# 在服务器配置中设置缓存头
Cache-Control: public, max-age=31536000
3.UI框架按需加载
按需引入组件,使用按需加载的方式引入 UI 框架中的组件,而不是一次性引入整个库。
// 使用 Babel 插件如 babel-plugin-import
import { Button, Select } from 'ant-design-vue';
4.图片资源的压缩
使用工具(如 ImageMagick、TinyPNG)在部署前压缩图片。
根据使用场景选择最佳的图片格式(如 WebP、JPEG、PNG)。
# 使用 TinyPNG API 压缩图片
tinypng -k YOUR_API_KEY input.png -o output.png
<img src="image.webp" alt="Example Image">
5.组件重复打包
避免组件重复打包
确保组件只被打包一次,可以通过以下方法避免
//方法 1
// 在 Webpack 中配置 alias,确保引用路径一致
module.exports = {
resolve: {
alias: {
'@components': path.resolve(__dirname, 'src/components/')
}
}
};
//方法2
//SplitChunksPlugin 配置
const path = require('path');
module.exports = {
entry: {//entry: 定义了入口文件,app 指向应用程序的入口文件
app: './src/index.js'
},
output: {//output: 定义了输出文件的名称和路径
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
},
optimization: {//Webpack 4 引入的配置,用于替代Webpack 3 的 CommonsChunkPlugin
splitChunks: {//用于拆分代码:运行时(指挥)/第三方(外包)/业务app(开发)
cacheGroups: {
vendor: {//cacheGroups.vendor: 用于提取第三方库,匹配 node_modules 文件夹中的文件,将它们提取到 vendors 文件中。
test: /[\\/]node_modules[\\/]/,
name: 'vendors',
chunks: 'all'
},
common: {//于提取 app 和 vendor 中共享的代码
name: 'common',
minChunks: 2,
chunks: 'all'
}
}
},
runtimeChunk: {//将 Webpack 运行时代码提取到 runtime 文件中
name: 'runtime'//指定运行时文件的名称为 runtime.js。
}
}
};
6.开启GZip压缩
在 Nginx 配置中启用 GZip
gzip on;
gzip_types text/plain application/javascript text/css;
7.使用SSR
如使用 Nuxt.js(Vue.js)或 Next.js(React)进行服务器端渲染。
// 使用 Nuxt.js 创建一个 SSR 项目
npx create-nuxt-app my-app
知识加油站
事件委托是一种通过利用事件冒泡机制,在父元素上统一处理多个子元素的事件监听的方法。这样可以减少 DOM 上的事件监听器数量,提升性能,特别是在需要处理大量相同类型事件的情况下。
事件冒泡机制
事件冒泡机制是指当一个事件触发时,它会从事件的目标元素开始,一直向上传递到最顶层的祖先元素。这意味着你可以在某个父元素上监听事件,然后统一或者特殊处理所有子元素的事件,而无需在每个子元素上单独添加事件监听器。
动态元素处理:对于动态添加或删除的子元素,无需重新绑定或解绑事件监听器,父元素上的事件监听器仍然有效。
//事件太多 浪费性能
<ul>
<li @click="handleClick">Item 1</li>
<li @click="handleClick">Item 2</li>
<li @click="handleClick">Item 3</li>
</ul>
<script>
export default {
methods: {
handleClick(event) {
console.log(event.target.textContent);
}
}
};
</script>
//利用委托 减少事件数量
<ul @click="handleClick">
<li>Item 1</li>
<li>Item 2</li>
<li>Item 3</li>
</ul>
<script>
export default {
methods: {
handleClick(event) {
const target = event.target;
if (target.tagName === 'LI') {
console.log(target.textContent);
}
}
}
};
</script>