# Lazy loading vue component
Trong bài viết trước (opens new window) mình có đề cập đến lazy load cho image. Bài viết này chúng ta tiếp tục với lazy load cho component nhé.
Về cơ bản chúng ta vẫn sử dụng IntersectionObserver API (opens new window) để xác định khi nào đến element nằm trong viewport. Và cộng thêm việc vuejs cung cấp async component (opens new window) nên ta hoàn toàn có thể kết hợp lại để lazy component.
Ý tưởng là ta sẽ handle loading state: https://vuejs.org/v2/guide/components-dynamic-async.html#Handling-Loading-State
const AsyncComponent = () => ({
// The component to load (should be a Promise)
component: import('./MyComponent.vue'),
// A component to use while the async component is loading
loading: LoadingComponent,
// A component to use if the load fails
error: ErrorComponent,
// Delay before showing the loading component. Default: 200ms.
delay: 200,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 3000
})
component: chúng ta sẽ cần return component cuối cùng(component muốn load) trong Promise để nó render ra cuối cùng
loading: chúng ta sẽ return một component với render
và mounted
để render ra 1 component hiển thị tạm thời trong lúc component chính chưa load xong và observer xem khi nào this.$el
nằm trong view port để load ra component muốn load
Ngoài ra bạn có thể viết thêm tuỳ ý 😄
Thôi không dài dòng nữa. Dưới đây là một ví dụ:
export default function lazyLoadComponent({
componentFactory,
loading,
loadingData,
}) {
let resolveComponent;
return () => ({
component: new Promise((resolve) => {
resolveComponent = resolve;
}),
loading: {
mounted() {
if(!('IntersectionObserver' in window)) {
componentFactory().then(resolveComponent);
return;
}
const observer = new IntersectionObserver((entries) => {
if (entries[0].intersectionRatio <= 0) return;
observer.unobserve(this.$el);
componentFactory().then(resolveComponent);
});
observer.observe(this.$el)
},
render(createElement) {
return createElement(loading, loadingData);
},
}
})
}
Và khi cần khai báo component:
export default {
components: {
HelloWorld: lazyLoadComponent({
loading: SkeletonBox,
loadingData: {
props: {
width: '100%',
height: '20em'
}
},
componentFactory: () => import('@/components/HelloWorld.vue'),
}),
}
}
SkeletonBox là một component để hiển thị tạm thời trong lúc component chính chưa load xong:
<template>
<span
:style="{ height, width: computedWidth }"
class="SkeletonBox"/>
</template>
<script>
export default {
name: 'SkeletonBox',
props: {
maxWidth: {
type: Number,
required: false,
default: 100
},
minWidth: {
type: Number,
required: false,
default: 80
},
height: {
type: String,
required: false,
default: '1em'
},
width: {
type: String,
required: true
}
},
computed: {
computedWidth() {
const randomWidth = Math.floor((Math.random() * (this.maxWidth - this.minWidth)) + this.minWidth);
return this.width || `${randomWidth}%`;
}
}
}
</script>
<style lang="scss" scoped>
.SkeletonBox {
display: inline-block;
position: relative;
vertical-align: middle;
overflow: hidden;
background-color: #DDDBDD;
&::after {
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
transform: translateX(-100%);
background-image: linear-gradient(
90deg,
rgba(#fff, 0) 0,
rgba(#fff, 0.2) 20%,
rgba(#fff, 0.5) 60%,
rgba(#fff, 0)
);
animation: shimmer 5s infinite;
content: '';
}
@keyframes shimmer {
100% {
transform: translateX(100%);
}
}
}
</style>
Ngoài ra để cho đơn giản thì chúng ta có thể dùng vue-lazy-hydration (opens new window) cho nhanh gọn 😄 Tuy nhiên cũng nên hiểu để nếu cần custom còn biết cách 😄
Bài viết tham khảo từ: https://markus.oberlehner.net/blog/lazy-load-vue-components-when-they-become-visible/