手拉手带你用 Vue3 + VantUI 写一个移动端脚手架 系列三 (状态缓存管理与列表组件)
项目地址(持续迭代中):github.com/jyliyue/vit…
系列文章:
前言
上篇我们已经对移动端的布局策略做了介绍,本篇主要对该项目的 状态缓存管理体系 pinia 和 vue3 中 keep-alive 的应用做介绍,并带大家一起封装一个移动端常用的列表组件(包含 下拉刷新,上拉加载,加载完毕等等),将琐碎的列表状态属性内化,不需要在使用的地方定义一堆UI层相关的参数(例如: loading,finished... 等等),支持接口配置化,开发人员只需关注业务数据的获取和样式的编写,废话不多,马上开始!
状态缓存管理体系 Pinia
状态管理
本项目使用新版的状态管理 Pinia 代替 Vuex 做状态管理,比较直观的好处就是不用在区分 同步调用 和 异步调用 了,store 的修改动作 action 作为常规函数调用,而不是使用 dispatch 方法或者是 commit 去调用,当然最重要的还是对 TS 支持比较友好
本地缓存管理
关于本地缓存,大家最先想到的就是 localstorage ,项目使用 pinia-plugin-persistedstate 插件实现 store 中数据的持久化,统一管理本地缓存,这里建议大家不要在代码中直接使用 localstorage.setItem 设置缓存数据,看过太多项目设置缓存东一块西一块的,更新维护及其头疼
代码实现
- 安装插件
npm i pinia pinia-plugin-persistedstate
- 新建 store 目录模块
├── store
│ ├── index.js
│ └── modules
│ └── user.js
- 入口文件 index.js
// store 持久化
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
const pinia = createPinia()
pinia.use(piniaPluginPersistedstate)
export default pinia
- 模块文件 user.js
/**
* @description: 用户信息
*/
export const useUserStore = defineStore('user', {
// 开启数据持久化
persist: true,
state: () => ({
token: 'token',
count: 1
}),
getters: {
double() {
return this.count * 2
}
},
actions: {
setToken(data) {
this.token = data
}
}
})
页面使用
import { useUserStore } from '@/store/modules/user'
const user = useUserStore()
user.setToken('my token')
console.log(user.token) // my token
console.log(user.count) // 1
console.log(user.double) // 2
注意如果使用解构的写法,会失去响应式,需要使用 storeToRefs 进行解构
const user = useUserStore()
const { token, count } = storeToRefs(user)
KeepAlive 组件缓存
<KeepAlive>
是一个 Vue 的内置组件,它的功能是在多个组件间动态切换时 缓存 被移除的组件实例
我们可以使用 keepAlive 来实现组件的缓存,保持组件的状态,借助这一特性,我们封装一个<app-router-view>
组件,通过路由配置统一管理我们的页面组件缓存,并且能够自主控制缓存组件的销毁
关于 <app-router-view>
组件的封装,大家可以看我另一篇文章,有详细讲解,这里就不赘述了
传送门:5 分钟带你实现一个可控制缓存销毁的 keepAlive 组件
列表组件 <app-list>
思路
基于 Vue3 的特性,在封装组件时我们的思路都是把 UI逻辑 和 业务逻辑 做区分,UI层只包含和交互响应相关的变量,业务层只和包含我们的展示数据,然后我们定义一个类 ListModel,ui 属性和 data 属性承接我们的UI逻辑和业务逻辑,并把相关的动作方法都在这个类里定义好
基于上述思路,我们来编写这个类和与它相关联的组件
ListModel.js
import { reactive } from 'vue'
class ListModel {
constructor(options) {
this.data = reactive({
list: []
})
this.ui = reactive({
loading: false,
refreshing: false,
finished: false,
finishedText: '没有更多了',
successText: '刷新成功'
})
this.options = options
}
onLoad = () => {
this.options.getData().then((res) => {
this.data.list = this.data.list.concat(res.data)
this.ui.refreshing = false
this.ui.loading = false
if (this.data.list.length >= res.total) {
this.ui.finished = true
}
})
}
onRefresh = () => {
this.ui.finished = false
this.ui.loading = true
this.data.list = []
this.onLoad()
}
}
export default ListModel
需要注意的点是父组件需要把获取数据的方法作为 options 参数传进来,这里定义为 getData
<app-list>
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
import ListModel from '~/class/ListModle'
const props = defineProps({
options: {
type: Object,
default: () => {}
}
})
// 初始化列表模型
const listModel = new ListModel(props.options)
// 解构出UI层和业务层数据
const { ui, data } = listModel
// UI层解构出插件动作需要的参数
const { refreshing, successText, loading, finished, finishedText } = toRefs(ui)
// 业务层解构出列表数据
const { list } = toRefs(data)
// 模型层解构出方法
const { onRefresh, onLoad } = listModel
</script>
<template>
<div class="app-list">
<van-pull-refresh
v-model="refreshing"
:success-text="successText"
@refresh="onRefresh"
>
<van-list
v-model:loading="loading"
@load="onLoad"
:finished="finished"
:finished-text="finishedText"
:offset="100"
>
<slot name="content" :list="list"> </slot>
</van-list>
</van-pull-refresh>
</div>
</template>
<style lang="scss" scoped>
.app-list {
height: 100%;
overflow-y: auto;
}
</style>
这里有两个问题需要注意,可能其它同学也遇到过类似的
<van-list>
进入时数据初始化loading两遍的问题
这里主要的原因是默认的触底判定距离太小导致的,导致多loading了一次,只要把 <van-list>
的 offset 属性调大就好了
- 我们需要在父组件去做列表样式的定制开发,那我们就要拿到组件内部的列表数据 list,这种情况我们可以使用作用域插槽 ,就是在
<slot>
定义一个 list 属性把数据暴露出去
在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。
我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes
结合具名作用域插槽,我们的代码可以这样实现
子组件
<slot name="content" :list="list"> </slot>
父组件
<app-list :options="options">
<template #content="{ list }">
<van-cell
v-for="(item, index) in list"
:key="index"
:title="index"
/>
</template>
</app-list>
这样我们的组件就已经封装好了,接下来看看如何使用
组件的使用
组件使用方面我们只需要写好获取数据的方法以及写好列表样式就好了,这里我们简单点,直接使用 <van-cell>
做展示,用 promise 模拟了下数据请求,关于字段命名各位可以根据自己项目的需要修改
<script setup>
// This starter template is using Vue 3 <script setup> SFCs
const options = {
getData: () => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve({
data: new Array(20),
total: 30
})
}, 1000)
})
}
}
</script>
<template>
<app-page>
<app-list :options="options">
<template #content="{ list }">
<van-cell
v-for="(item, index) in list"
:key="index"
:title="index"
/>
</template>
</app-list>
</app-page>
</template>
<style lang="scss" scoped></style>
这样我们的基础版 <app-list>
就已经开发完成了,大家可以看看效果
好了,本篇对移动端项目的 状态缓存管理体系 和 列表组件<app-list>
的封装讲解告一段落,下一篇我们将继续对列表组件进行完善,实现进入详情返回列表时记录位置,并做一个支持多 Tab 列表切换,每个列表都能保持位置的案例,敬请大家期待!
项目地址(持续迭代中):github.com/jyliyue/vit…
系列文章:
转载自:https://juejin.cn/post/7156185820783706149