欢迎光临
我们一直在努力

Vue—— Vue3 懒加载与预加载策略

背景问题: 需要优化页面加载性能。

方案思考: 使用懒加载和预加载策略来平衡性能和用户体验。

具体实现: 图片懒加载指令:

// directives/lazy-image.js
export default {
mounted(el, binding) {
// 创建 Intersection Observer
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
// 图片进入视口,加载真实图片
const img = new Image()
img.onload = () => {
el.src = binding.value
el.classList.remove('lazy-img–loading')
el.classList.add('lazy-img–loaded')
observer.unobserve(el)
}
img.src = binding.value

// 添加加载状态样式
el.classList.add('lazy-img–loading')
el.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2NjYyIvPjwvc3ZnPg==' // 占位图
}
})
})

observer.observe(el)
},

updated(el, binding) {
// 当绑定值变化时更新
if (binding.value !== binding.oldValue) {
el.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwIiBoZWlnaHQ9IjEwMCIgZmlsbD0iI2NjYyIvPjwvc3ZnPg=='
el.classList.remove('lazy-img–loaded')

const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = new Image()
img.onload = () => {
el.src = binding.value
el.classList.remove('lazy-img–loading')
el.classList.add('lazy-img–loaded')
observer.unobserve(el)
}
img.src = binding.value
el.classList.add('lazy-img–loading')
}
})
})

observer.observe(el)
}
}
}

组件懒加载:

<!– components/LazyComponent.vue –>
<template>
<div class="lazy-component">
<div v-if="loading" class="loading-placeholder">
<el-skeleton :rows="5" />
</div>
<component v-else :is="dynamicComponent" v-bind="componentProps" />
</div>
</template>

<script setup>
import { ref, defineAsyncComponent } from 'vue'

const props = defineProps({
componentPath: {
type: String,
required: true
},
componentProps: {
type: Object,
default: () => ({})
}
})

const loading = ref(true)
const dynamicComponent = ref(null)

// 动态加载组件
const loadComponent = async () => {
try {
// 使用 defineAsyncComponent 进行懒加载
dynamicComponent.value = defineAsyncComponent({
loader: () => import(`@/components/${props.componentPath}.vue`),
loadingComponent: null, // 我们自己处理加载状态
errorComponent: null, // 我们自己处理错误
delay: 200, // 延迟显示加载状态
timeout: 3000 // 超时时间
})
} catch (error) {
console.error('组件加载失败:', error)
} finally {
loading.value = false
}
}

loadComponent()
</script>

<style scoped>
.loading-placeholder {
padding: 20px;
}
</style>

预加载策略:

// utils/prefetch.js
// 预加载工具类
export class PrefetchUtil {
constructor() {
this.prefetchedResources = new Set()
}

// 预加载脚本
async prefetchScript(src) {
if (this.prefetchedResources.has(src)) {
return
}

return new Promise((resolve, reject) => {
const link = document.createElement('link')
link.rel = 'prefetch'
link.href = src
link.onload = resolve
link.onerror = reject

document.head.appendChild(link)
this.prefetchedResources.add(src)
})
}

// 预加载组件
async prefetchComponent(componentPath) {
if (this.prefetchedResources.has(componentPath)) {
return
}

try {
await import(`@/components/${componentPath}.vue`)
this.prefetchedResources.add(componentPath)
} catch (error) {
console.error('预加载组件失败:', error)
}
}

// 预加载路由
async prefetchRoute(routeName) {
// 这里可以根据路由名称预加载对应组件
const routeModule = await import(`@/views/${routeName}.vue`)
return routeModule
}

// 根据用户行为预测预加载
predictAndPrefetch(routes) {
// 基于用户历史行为或路由关系预测可能访问的页面
routes.forEach(route => {
setTimeout(() => {
this.prefetchRoute(route)
}, 2000) // 延迟预加载,避免影响当前页面性能
})
}

// 清除预加载缓存
clearCache() {
this.prefetchedResources.clear()
}
}

// 创建全局预加载实例
export const prefetchUtil = new PrefetchUtil()

虚拟滚动实现:

<!– components/VirtualList.vue –>
<template>
<div
ref="containerRef"
class="virtual-list"
:style="{ height: containerHeight + 'px' }"
@scroll="handleScroll"
>
<div :style="{ height: totalHeight + 'px' }" class="virtual-list__spacer">
<div
v-for="item in visibleItems"
:key="item.id"
:style="{
height: itemHeight + 'px',
transform: `translateY(${item.index * itemHeight}px)`
}"
class="virtual-list__item"
>
<slot :item="item" :index="item.index" />
</div>
</div>
</div>
</template>

<script setup>
import { ref, computed, onMounted, onUnmounted } from 'vue'

const props = defineProps({
items: {
type: Array,
required: true
},
itemHeight: {
type: Number,
default: 50
},
containerHeight: {
type: Number,
default: 400
}
})

const containerRef = ref(null)
const scrollTop = ref(0)

// 计算可见项
const visibleRange = computed(() => {
const startIndex = Math.floor(scrollTop.value / props.itemHeight)
const visibleCount = Math.ceil(props.containerHeight / props.itemHeight)
const endIndex = Math.min(startIndex + visibleCount + 5, props.items.length) // 多渲染几个以防万一

return {
start: Math.max(0, startIndex),
end: endIndex
}
})

// 可见项数据
const visibleItems = computed(() => {
return props.items
.slice(visibleRange.value.start, visibleRange.value.end)
.map((item, index) => ({
…item,
index: visibleRange.value.start + index
}))
})

// 总高度
const totalHeight = computed(() => props.items.length * props.itemHeight)

// 处理滚动
const handleScroll = () => {
scrollTop.value = containerRef.value.scrollTop
}

onMounted(() => {
scrollTop.value = containerRef.value.scrollTop
})
</script>

<style scoped>
.virtual-list {
overflow-y: auto;
position: relative;
}

.virtual-list__spacer {
position: relative;
width: 100%;
}

.virtual-list__item {
position: absolute;
left: 0;
right: 0;
display: flex;
align-items: center;
padding: 0 16px;
border-bottom: 1px solid #eee;
}
</style>

赞(0)
未经允许不得转载:171主机测评 » Vue—— Vue3 懒加载与预加载策略
分享到: 更多 (0)

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址