大家好,我是老码,今天咱们来聊聊怎么用Vue的懒加载和异步组件,给电商网站的首屏提提速。想象一下,用户打开你的网站,慢得像蜗牛爬,那绝对是灾难。没人愿意等,都跑去竞争对手那里了。所以,速度就是生命!
咱们的目标很明确:让用户以最快的速度看到网站的基本框架和核心内容,让他们觉得“哎,这个网站还不错,速度挺快的”,然后剩下的东西慢慢加载,用户根本感觉不到。
第一招:路由懒加载
路由懒加载,顾名思义,就是等到需要的时候才加载对应的路由组件。这就像你去餐厅吃饭,不是一口气把所有菜都端上来,而是你点哪个菜,厨师才做哪个菜。
原理:
Vue Router默认情况下会一次性加载所有路由对应的组件。但是,对于大型应用来说,这样做会显著增加初始加载时间。懒加载就是把组件的加载时机推迟到路由被访问时。
实现方式:
主要有三种方式:
-
webpack 的 import() 语法(推荐)
这是最常用,也是最推荐的方式。
import()
会返回一个 Promise,webpack 会自动分割代码,生成独立的 chunk 文件。const routes = [ { path: '/home', component: () => import('../views/Home.vue') }, { path: '/category', component: () => import('../views/Category.vue') }, { path: '/product/:id', component: () => import('../views/ProductDetail.vue') } ];
简单吧?只需要把
component
属性的值改成一个返回import()
的函数就行了。当用户访问/home
时,才会加载Home.vue
组件。 -
Vue 的异步组件
Vue 提供了
Vue.component
的异步版本,可以用来注册异步组件。Vue.component('async-example', function (resolve, reject) { setTimeout(function () { // 将组件定义传入 resolve 回调函数 resolve({ template: '<div>I am async!</div>' }) }, 1000) })
这种方式比较原始,不太常用,因为
import()
语法更简洁方便。 -
结合 require.ensure (不推荐,webpack 已经废弃)
这种方式是 webpack 早期提供的,现在已经不推荐使用,因为它已经被
import()
取代了。
代码示例:
假设我们有一个电商网站,包含首页、分类页、商品详情页、购物车等页面。
src/router/index.js
import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter)
const routes = [
{
path: '/',
name: 'Home',
component: () => import('../views/Home.vue')
},
{
path: '/category',
name: 'Category',
component: () => import('../views/Category.vue')
},
{
path: '/product/:id',
name: 'ProductDetail',
component: () => import('../views/ProductDetail.vue')
},
{
path: '/cart',
name: 'Cart',
component: () => import('../views/Cart.vue')
},
{
path: '/profile',
name: 'Profile',
component: () => import('../views/Profile.vue')
}
]
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
})
export default router
这样,只有当用户访问对应的路由时,才会加载相应的组件,大大减少了首屏加载时间。
第二招:组件异步加载
除了路由组件,页面中一些非关键的组件也可以进行异步加载。比如,一些弹窗、模态框、或者是一些不常用的功能组件。
原理:
和路由懒加载类似,组件异步加载也是将组件的加载时机推迟到需要的时候。
实现方式:
也是使用 import()
语法,只不过是在组件注册或者使用的地方。
// 全局注册异步组件
Vue.component(
'async-component',
() => import('./components/AsyncComponent.vue')
)
// 局部注册异步组件
export default {
components: {
AsyncComponent: () => import('./components/AsyncComponent.vue')
}
}
代码示例:
假设我们的商品详情页有一个“分享”按钮,点击后弹出一个分享模态框。这个模态框不是必需的,可以异步加载。
src/views/ProductDetail.vue
<template>
<div>
<h1>商品详情</h1>
<p>商品名称:{{ product.name }}</p>
<p>商品价格:{{ product.price }}</p>
<button @click="showShareModal">分享</button>
<share-modal v-if="showModal" @close="closeModal"></share-modal>
</div>
</template>
<script>
export default {
data() {
return {
product: {
name: '超级棒的商品',
price: 99.99
},
showModal: false
}
},
components: {
ShareModal: () => import('../components/ShareModal.vue')
},
methods: {
showShareModal() {
this.showModal = true;
},
closeModal() {
this.showModal = false;
}
}
}
</script>
注意,这里我们使用 v-if
来控制 ShareModal
组件的显示,只有当 showModal
为 true
时,才会加载并渲染 ShareModal
组件。
第三招:骨架屏(Skeleton Screen)
骨架屏是在数据加载完成之前,先显示一个简单的页面结构,给用户一种“正在加载”的心理暗示。这就像你去餐厅吃饭,服务员先给你端上一杯水和餐巾纸,让你知道他们正在准备你的饭菜。
原理:
在数据加载期间,显示一个静态的占位符,模拟真实的内容结构。
实现方式:
-
手动编写骨架屏组件
这是最灵活的方式,可以根据实际页面的结构,编写相应的骨架屏组件。
<template> <div class="skeleton"> <div class="skeleton-title"></div> <div class="skeleton-content"></div> <div class="skeleton-image"></div> </div> </template> <style scoped> .skeleton { /* 骨架屏样式 */ } .skeleton-title { /* 标题样式 */ } .skeleton-content { /* 内容样式 */ } .skeleton-image { /* 图片样式 */ } </style>
-
使用现成的骨架屏组件库
有很多现成的 Vue 骨架屏组件库可以使用,比如
vue-skeleton-loader
、vue-content-placeholders
等。npm install vue-skeleton-loader
<template> <div> <vue-skeleton-loader v-if="loading" type="list" :number="3" animation="wave"></vue-skeleton-loader> <div v-else> <!-- 真实内容 --> </div> </div> </template> <script> import VueSkeletonLoader from 'vue-skeleton-loader' export default { components: { VueSkeletonLoader }, data() { return { loading: true } }, mounted() { // 模拟数据加载 setTimeout(() => { this.loading = false }, 2000) } } </script>
代码示例:
假设我们的首页需要加载商品列表。
src/views/Home.vue
<template>
<div>
<h1>首页</h1>
<div v-if="loading">
<!-- 骨架屏 -->
<div class="product-skeleton" v-for="i in 6" :key="i">
<div class="product-image-skeleton"></div>
<div class="product-title-skeleton"></div>
<div class="product-price-skeleton"></div>
</div>
</div>
<div v-else class="product-list">
<!-- 商品列表 -->
<div class="product-item" v-for="product in products" :key="product.id">
<img :src="product.image" alt="product.name">
<h3>{{ product.name }}</h3>
<p>{{ product.price }}</p>
</div>
</div>
</div>
</template>
<script>
export default {
data() {
return {
loading: true,
products: []
}
},
mounted() {
// 模拟数据加载
setTimeout(() => {
this.products = [
{ id: 1, name: '商品1', price: 10.99, image: 'https://via.placeholder.com/150' },
{ id: 2, name: '商品2', price: 20.99, image: 'https://via.placeholder.com/150' },
{ id: 3, name: '商品3', price: 30.99, image: 'https://via.placeholder.com/150' }
];
this.loading = false;
}, 2000);
}
}
</script>
<style scoped>
.product-list {
display: flex;
flex-wrap: wrap;
}
.product-item {
width: 200px;
margin: 10px;
border: 1px solid #ccc;
padding: 10px;
}
.product-skeleton {
width: 200px;
margin: 10px;
border: 1px solid #ccc;
padding: 10px;
background-color: #f0f0f0;
}
.product-image-skeleton {
width: 100%;
height: 150px;
background-color: #ddd;
}
.product-title-skeleton {
width: 80%;
height: 20px;
background-color: #ddd;
margin-top: 10px;
}
.product-price-skeleton {
width: 50%;
height: 20px;
background-color: #ddd;
margin-top: 10px;
}
</style>
第四招:预加载(Preload)和预取(Prefetch)
预加载和预取都是优化页面加载速度的技术,但它们的用途略有不同。
预加载(Preload):
预加载告诉浏览器,尽快加载当前页面需要的资源,比如图片、字体、CSS、JavaScript 等。这可以避免资源加载的延迟,提高页面的渲染速度。
预取(Prefetch):
预取告诉浏览器,加载用户将来可能访问的页面需要的资源,比如下一个页面或者某个组件。这可以提前准备好资源,当用户访问这些页面时,就可以立即显示,无需等待。
实现方式:
-
使用
<link>
标签<!-- 预加载 --> <link rel="preload" href="image.png" as="image"> <link rel="preload" href="style.css" as="style"> <link rel="preload" href="script.js" as="script"> <!-- 预取 --> <link rel="prefetch" href="next-page.html">
-
使用 webpack 的
preload
和prefetch
magic commentsimport(/* webpackPreload: true */ './moduleA.js') // 预加载 import(/* webpackPrefetch: true */ './moduleB.js') // 预取
代码示例:
假设我们的商品详情页需要加载一些图片和字体。
public/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<title><%= htmlWebpackPlugin.options.title %></title>
<!-- 预加载图片和字体 -->
<link rel="preload" href="<%= BASE_URL %>img/product-image.jpg" as="image">
<link rel="preload" href="<%= BASE_URL %>fonts/my-font.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预取下一个页面 -->
<link rel="prefetch" href="<%= BASE_URL %>category">
</head>
<body>
<noscript>
<strong>We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong>
</noscript>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>
第五招:代码分割(Code Splitting)
代码分割是将应用程序的代码分割成多个小的 chunk 文件,按需加载。这可以减少初始加载的文件大小,提高首屏加载速度。
原理:
将大型 JavaScript 文件分割成多个小的文件,只加载当前页面需要的代码。
实现方式:
-
使用 webpack 的 entry points
可以定义多个 entry points,每个 entry point 对应一个独立的 chunk 文件。
-
使用动态 import()
动态
import()
会自动分割代码,生成独立的 chunk 文件。 -
使用 webpack 的 SplitChunksPlugin
SplitChunksPlugin 可以自动分割代码,提取公共模块,减少重复代码。
代码示例:
webpack.config.js
module.exports = {
entry: {
app: './src/main.js',
vendor: ['vue', 'vue-router'] // 将 Vue 和 Vue Router 提取到 vendor chunk
},
optimization: {
splitChunks: {
cacheGroups: {
vendor: {
test: /[\/]node_modules[\/]/,
name: 'vendors',
chunks: 'all'
}
}
}
}
};
总结:
优化策略 | 原理 | 实现方式 | 适用场景 |
---|---|---|---|
路由懒加载 | 延迟加载路由组件,按需加载 | 使用 import() 语法 |
大型单页应用,有多个路由,但初始加载时不需要所有路由组件 |
组件异步加载 | 延迟加载非关键组件,按需加载 | 使用 import() 语法 |
页面中包含一些非必需的组件,比如弹窗、模态框等 |
骨架屏 | 在数据加载期间显示占位符,提供用户体验 | 手动编写骨架屏组件,或者使用现成的骨架屏组件库 | 需要加载数据才能显示内容的页面,比如商品列表、详情页等 |
预加载和预取 | 提前加载资源,减少延迟 | 使用 <link> 标签,或者使用 webpack 的 preload 和 prefetch magic comments |
需要加载一些关键资源(比如图片、字体)的页面,或者用户将来可能访问的页面 |
代码分割 | 将代码分割成多个小的 chunk 文件,按需加载 | 使用 webpack 的 entry points,或者使用动态 import() ,或者使用 webpack 的 SplitChunksPlugin |
大型单页应用,代码量很大,需要分割成多个文件 |
这些优化策略不是孤立的,可以结合使用,以达到最佳的优化效果。
注意事项:
- 过度优化: 不要为了优化而优化,过度优化可能会导致代码复杂度增加,维护成本提高。
- 性能测试: 在进行优化后,一定要进行性能测试,验证优化效果。
- 用户体验: 在进行优化时,要考虑到用户体验,不要让用户感到困惑或者不舒服。
好了,今天的分享就到这里。希望这些技巧能帮助你打造一个飞快的电商网站!记住,速度就是生命,用户体验至上! 下次有机会再和大家聊聊其他前端优化技巧。拜拜!