ts+hooks+pinia = 技术亮点之懒加载!!
前言
作为一名程序员,我们简历上出现率极高的三个字就是'技术栈',在技术栈中我们需要提及我们使用了什么技术,去实现我们的项目功能,今日Virtual09我呢,学习了ts+hooks+pinia实现懒加载进行项目性能优化,这不,我呢就迫不及待的和大家分享这个技术亮点了吗。
正文
了解下ts,hooks,pinia
什么是ts?
ts全称TypeScript,是一种来源的编程语言,是JavaScript的超集.所以在ts中是可以兼容js代码的.在JavaScript中是没有静态类型系统和其他高级特性的,但是ts的到来,为js提供了静态类型的检查,因为ts允许开发者为变量,函数等参数和返回值等指定类型,从而在编译阶段发现潜在额的类型错误,这一特性大大降低了开发者代码敲错的概率。
什么是hooks?
Hooks是一种在函数式编程中使用的模式,也就是说hooks函数式编程是开发者开发经验的一种体现。在函数式编程中,Hooks可以被看做是一种特殊的函数,它们可以和框架或者库提供的特定功能“挂钩”。但是Hooks函数式编程的真正目的还是减少类组件的使用,提高代码的可读性和可复用性。
什么是pinia?
开始写代码
刚刚跟大家bb了那麽多,现在咱就正式开始写代码。
准备工作
使用npm指令创建ts想项目。自定义项目名称,选择vue框架,语言选择ts
npm init vite
创建好项目后,我们在终端安装下第三方库'pinia'
npm i pinia
现在操练起来,干就完了!!
在'src'文件下我们创建三个文件夹,一个是hooks
,一个是store
,最后一个是types
.首先让我们先进入store
中,搭建中央数据库,创建文件articles.ts
import {defineStore} from 'pinia';
从pinia中解构出defineStore
,定义一个仓库useArticleStore
,并向外抛出
export const useArticleStore = defineStore('article',()=>{})
我们拟一个假数据,使用_
标注一下这是一个私有化数据,不向外输出(这里假数据比较长,就截取一条作为展示)
// 文章数据集
const _articles = [
{
id:1,
title:"恭喜王总拿下阿里巴巴"
},......
]
我们的假数据私有化,不向外提供(这里,不向外提供,是指_articles这个变量不向外提供),也就意味着,我们是不是需要一个载体来将私有化数据向外运输啊,而且页面滑动,数据是会加载的,所以这个运输工具也要是响应式数据对吧。
// 响应式文章数据
const articles = ref<Article[]>([])
看到这里,大家是不是会有一个大大的问号<Article>
是啥啊?诶,这我就可以把ts给搬出来给大家介绍下了。ts可以支持用户自定义数据类型,所以这个<Article>
就是我们自定的一个数据类型,我们在之前创建的文件夹types
中,创建一个Artilce.ts
文件
export type Article = {
id:number;
title:string;
}
这个文件用于创建一个自定义的数据类型,并向外抛出.这里我们引入时,也要注意,我们引入的是一个类型声明文件,所以我们要在import
后面加一个type
import type { Article } from '../types/Article';
这里我们对于这种响应式数据可以大胆的加上泛型限制数据类型,加上泛型之后,响应式的数据就必须严格按照我们自己声明的数据格式。
const getArticles = (page:number, size:number = 10) => {
return new Promise<{
data:Article[];
page:number;
total:number;
hasMore:boolean;
}>((resolve => {
setTimeout(()=>{
const data = _articles.slice((page-1)*size,page * size);
articles.value = [...articles.value,...data];
resolve({
data,
page,
total:_articles.length,
hasMore:page * size < _articles.length
});
}, 500);
}))
return{
articles,
getArticles
}
page
:指代的是页面为第几页
size
:指代的是一页可以容纳多少条数据
在new Promise
中我们使用泛型,规定了其返回的数据格式。
setTimeOut(()=>{},500)
定时器,模拟异步操作。
const data = _articles.slice((page-1)*size,page * size);
表示我们按页切割,得到当前页面数据。
articles.value = [...articles.value,...data];
表示追加数据,这个articles我们是要抛出给页面加载的,这里就是展开运算符的一个应用了。这样我们就基本把中央数据库写好了。然后再main.ts
中引入一下
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import {createPinia} from 'pinia'
const app = createApp(App)
app
.use(createPinia())
.mount('#app')
IntersectionObserver
什么是IntersectionObserver
IntersectionObserver
是一个 JavaScript API,它允许开发者监控一个或多个元素与另一个元素或视口之间的交叉变化。当被观察的元素进入或离开另一个元素(或视口)的可视区域内时,IntersectionObserver
会通知开发者。
引入一些必要的API,以及类型声明
import { watch, onUnmounted, ref } from "vue"
import type { Ref } from "vue"
我们声明一个函数useIntersectionObserver
nodeRef: Ref<HTMLElement | null>,
loadMore: () => void
)
接收两个参数,第一个是响应式类型,类型是html节点,或是null,第二个参数表示接受一个函数。
使用联合数据类型实例化一个IntersectionObserver
let observer: IntersectionObserver | null = null
watch(nodeRef,(newNodeRef, oldNodeRef) => {
if(oldNodeRef && observer ){
observer.unobserve(oldNodeRef)
}
if(newNodeRef){
observer = new IntersectionObserver(([entry]) => {
if(entry.isIntersecting){
loadMore()
}
})
observer.observe(newNodeRef);
}
})
这里watch
会对于nodeRef
进行监听,其值发生变化时就会执行.
if(oldNodeRef && observer )
当存在旧节点和obersver
都存在时,就取消对旧节点的监听
if(newNodeRef)
创建一个新的观察者,使用[entry]
解构参数entries
,获取第一个元素,并判断这个节点的第一个元素是否在视口内,在的话就加载更多。最后使用observer
监听新的节点
当上述代码执行完之后,我们就需要做一个操作,就是防止内存泄漏
onUnmounted(()=>{
if(observer){
observer.disconnect();
}
})
然后就是监听判断是否需要加载更多
watch(hasMore, (value)=>{
if(observer){
if(value && nodeRef.value){
observer.observe(nodeRef.value);
}else{
observer.disconnect();
}
}
})
返回数据
return{
hasMore,
setHasMore: (value: boolean)=>{
hasMore.value = value;
},
}
我们提供的setHasMore
是用于判断是否需要加载更多,同多value值进行判断为true则进行加载更多。
最后别忘了抛出
export default useIntersectionObserver;
页面逻辑执行
引入相应文件
import { ref, onMounted, toRefs } from 'vue'
import {useArticleStore } from './store/article.ts'
import useIntersectionObserver from './hooks/useIntersctionObserver.ts'
创建一个仓库
const articleStore = useArticleStore()
初始化第一页的数据
onMounted(async () => {
await articleStore.getArticles(1)
})
从 articleStore
中提取响应式的 articles
属性,并将其转换为响应式引用。
const { articles } = toRefs(articleStore)
toRefs
是 Vue 3 中的一个实用工具函数,它用于将一个具有响应式属性的对象拆分为单独的响应式引用。这在你想要从一个响应式对象中提取特定属性并将其用作单独的响应式引用时非常有用
参数定义
//设置节点
const itemRef = ref<HTMLElement|null>(null);
//是否加载更多,我们默认加载更多
let hasMore = ref(true)
//定义当前页数 为1
const currentPage = ref<number>(1)
且看我们如何执行加载下一页的数据,以及hasMore
const handleNextPage = async (setHasMore:(value:boolean)=>void)=>{
currentPage.value++;
const res = await articleStore.getArticles(currentPage.value);
if(!res.hasMore){
setHasMore(false)
hasMore.value = false
}
}
这里是一个异步操作,返回一个Promise
对象
我们设定了Promise
对象中存在一个hasMore
的方法这个hasMore
表示,当artilces
中的数据长度,会大于当前总页面打数据时,就会继续加载。if(!res.hasMore)
值为false
时,则不需要加载了。
对节点进行监听
//监听节点
const {setHasMore} = useIntersectionObserver(itemRef,()=>{
handleNextPage(setHasMore);
})
这里我们就要回到useIntersectionObserver
中
接受两个参数,一个表示当前的元素节点,一个是函数,表示是否需要加载更多
当节点更新,就会走到这里,判断该节点nodeRef
的第一个元素是否进入到视口内,进入的话就会执行加载更多
vue中我们需要的是通过动态绑定ref
来判断是否更新节点
<template>
<section>
<article
class="item"
v-for="(item,index) in articles"
:key="item.id"
:ref="(el)=>(index === articles.length-1?(itemRef = el as HTMLElement):'')">
<div >{{item.title}}</div>
</article>
<div v-if="!hasMore">
没有更多数据
</div>
</section>
</template>
<style scoped>
.item{
height: 20vh;
}
</style>
当我们加载的数据的下标会等于我们应该输出的内容的长度-1,是不是就意味着我们加载完了这一页的数据,就要更新节点了。 效果展示
本文的项目亮点就介绍到这里了,感谢大家阅读!!希望大家能留下一个宝贵的赞!!
转载自:https://juejin.cn/post/7395866502716424227