手拉手带你用 Vue3 + VantUI 写一个移动端脚手架 系列三 (状态缓存管理与列表组件)

简介: 状态缓存管理与列表组件

ef6742cdabe8fc0ad8f04fdf4a5b4f3.png

项目地址(持续迭代中):github.com/jyliyue/vit…

系列文章:

前言

上篇我们已经对移动端的布局策略做了介绍,本篇主要对该项目的 状态缓存管理体系 pinia 和 vue3 中 keep-alive 的应用做介绍,并带大家一起封装一个移动端常用的列表组件(包含 下拉刷新,上拉加载,加载完毕等等),将琐碎的列表状态属性内化,不需要在使用的地方定义一堆UI层相关的参数(例如: loading,finished... 等等),支持接口配置化,开发人员只需关注业务数据的获取和样式的编写,废话不多,马上开始!

状态缓存管理体系 Pinia

022754043b7053dff2f8a81d904e455.png

状态管理

本项目使用新版的状态管理 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 组件缓存

是一个 Vue 的内置组件,它的功能是在多个组件间动态切换时 缓存 被移除的组件实例

我们可以使用 keepAlive 来实现组件的缓存,保持组件的状态,借助这一特性,我们封装一个 组件,通过路由配置统一管理我们的页面组件缓存,并且能够自主控制缓存组件的销毁

50175f66bfe3c92c25ac19af328a860.png

关于  组件的封装,大家可以看我另一篇文章,有详细讲解,这里就不赘述了

传送门:5 分钟带你实现一个可控制缓存销毁的 keepAlive 组件

列表组件

dc53c6089ae1cb4bf537f69fe488a5a.png

思路

基于 Vue3 的特性,在封装组件时我们的思路都是把 UI逻辑业务逻辑 做区分,UI层只包含和交互响应相关的变量,业务层只和包含我们的展示数据,然后我们定义一个类 ListModelui 属性和 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,这种情况我们可以使用作用域插槽 ,就是在 定义一个 list 属性把数据暴露出去

在某些场景下插槽的内容可能想要同时使用父组件域内和子组件域内的数据。要做到这一点,我们需要一种方法来让子组件在渲染时将一部分数据提供给插槽。

我们也确实有办法这么做!可以像对组件传递 props 那样,向一个插槽的出口上传递 attributes

947f06c0dbcbb3f5dafe4840928467d.png

结合具名作用域插槽,我们的代码可以这样实现

子组件

<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>
复制代码

这样我们的组件就已经封装好了,接下来看看如何使用

组件的使用

组件使用方面我们只需要写好获取数据的方法以及写好列表样式就好了,这里我们简单点,直接使用  做展示,用 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>
复制代码

这样我们的基础版  就已经开发完成了,大家可以看看效果

c070c6f550b63f6553d4d0ee6a62b7c.png

好了,本篇对移动端项目的 状态缓存管理体系列表组件 的封装讲解告一段落,下一篇我们将继续对列表组件进行完善,实现进入详情返回列表时记录位置,并做一个支持多 Tab 列表切换,每个列表都能保持位置的案例,敬请大家期待!

项目地址(持续迭代中):github.com/jyliyue/vit…

系列文章:


相关文章
|
20天前
|
JavaScript
Vue 父传子组件传参 defineProps
Vue 父传子组件传参 defineProps
|
20天前
|
JavaScript 前端开发
Vue 创建组件
Vue 创建组件
|
20天前
|
JavaScript
vue element plus Descriptions 描述列表
vue element plus Descriptions 描述列表
42 0
|
20天前
|
JavaScript
vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)
vue路由导航守卫(全局守卫、路由独享守卫、组件内守卫)
35 0
|
8天前
|
JavaScript
ant design vue 在列表中使用插槽 例如当性别为0的时候在列表中我想显示男
ant design vue 在列表中使用插槽 例如当性别为0的时候在列表中我想显示男
8 0
|
11天前
|
JavaScript
Vue Steps步骤组件用法
Vue Steps步骤组件用法
14 0
|
16天前
|
JavaScript
vue 组件注册
vue 组件注册
|
16天前
|
JavaScript
vue 组件事件
vue 组件事件
|
16天前
|
JavaScript 前端开发 索引
vue 列表渲染
vue 列表渲染
|
1月前
|
缓存 NoSQL 安全
【Redis】缓存穿透
【Redis】缓存穿透
30 0