上兩篇介紹了如何用vite2 + Vue3 搭建一個博客網站,以及輕量級狀態的基礎使用,那么二者結合起來會發生什么呢?
做個開源博客學習Vite2 + Vue3 (四)實現博客功能 https://www.cnblogs.com/jyk/p/14696474.html
制作一個輕量級的狀態管理插件:Vue-data-state https://www.cnblogs.com/jyk/p/14706005.html
回顧博客代碼
博客代碼里面有三個列表:首頁的博文列表、編輯博文里面的博文列表以及討論列表。
三個列表的寫了三份代碼,但是對比看一下就會發現,這三份代碼大同小異嘛。
其共同點就是:查詢條件、分頁要求、數據容器。
那么是不是可以針對這幾個共同點抽象一下,做成一個共用的函數呢?
這個就需要用到輕量級狀態里面的局部狀態了。
為啥一定要用狀態管理呢?那是因為可以把不同的功能分布到不同的組件里面,而不用拘泥在一個組件內實現全部功能。
比如把查詢條件的表單放在單獨的組件里面,這樣可以簡化列表組件的代碼,更容易進行管理。
定義用於列表需求的局部狀態
// store-ds/index.js
import VuexDataState from 'vue-data-state'
// 設置全局狀態和局部狀態
export default VuexDataState.createStore({
global: { // 全局狀態
onlineUser: { // 當前登錄用戶
name:'jyk' //
}
},
local: { // 局部狀態
// 數據列表,使用前需要先注冊
dataListState() { // 顯示數據列表的狀態
return { // 確保不會重復
findKind: {}, // 查詢方式,僅容器,不用寫具體的查詢字段
find: {}, // 查詢關鍵字,還是容器
page: { // 分頁參數
pageTotal: 100,
pageSize: 2,
pageIndex: 1, // 其實主要是用這個
orderBy: { id: false } // 排序字段,可以寫多個
},
_query: {}, // 緩存的查詢條件,翻頁的時候使用
isReload: false // 重新加載數據,需要統計總數
}
}
},
init(state) {
// 初始化
}
})
-
dataListState
定義一個數據列表用的狀態,局部狀態的優點就是可以在“多套”業務組件里復用,而且可以保證局部狀態不會相互影響,博文列表組件、討論列表組件都可以用這個狀態,而不用定義多個。 -
findKind
查詢方式,這個只定義一個容器,具體的內容在后面的代碼里面實現。 -
find
查詢關鍵字,記錄用戶輸入的查詢內容。具體內容還是在后面的代碼里面實現。 -
Page
分頁信息,這里主要使用 pageIndex,其他的算是附贈吧,畢竟一般都是配套出現的。 -
_query
緩存查詢條件,用戶進行查詢的時候需要記錄查詢條件,然后翻頁的時候就可以直接拿出來使用了。
緩存起來也便於確定需要哪些查詢條件。 -
init
初始化狀態,這個是給全局狀態用的。
MVC 的 Control
然后我們可以借鑒MVC的思路,做一個control,控制model的加載、狀態的變化等功能。
建立一個 src/control 文件夾,統一管理相關的代碼。
// control/data-list.js
import { watch, reactive } from 'vue'
// 狀態
import VueDS from 'vue-data-state'
// webSQL
import { webSQLVue } from 'nf-web-storage'
// 獲取配置
// eslint-disable-next-line import/no-absolute-path
import blogListInfo from '/config/bloglist.config.json'
/**
* 數據列表的通用管理類
* * 查詢條件
* * 分頁信息
* * 加載數據
* * 注入局部狀態
*/
export default function dataListControl (jsonFlag) {
// 顯示數據列表的數組
const _dataList = reactive([])
// 訪問 webSQL
const { help } = webSQLVue.useHelp()
// 訪問狀態
const { reg, get } = VueDS.useStore()
// 子組件里面獲取狀態
const dataListState = get.dataListState()
// 父組件注冊狀態
const regDataListState = () => {
// 注入獲取列表需要的狀態,便於查詢、分頁里面修改
const state = reg.dataListState()
// 需要的配置信息
const listInfo = blogListInfo[jsonFlag]
if (typeof listInfo === 'undefined') {
// 沒有設置對應的信息
return state
}
// 設置具體的查詢條件和查詢方式
state.find = listInfo.find
state.findKind = listInfo.findKind
state.page = listInfo.page
// 重新加載數據
watch(() => state.isReload, () => {
const _query = {}
// 設置查詢條件
for (const key in state.find) {
const value = state.find[key]
const kind = state.findKind[key]
if (value && value.length > 0 && value > 0) {
_query[key] = [kind, value]
}
}
// 緩存查詢條件,分頁的時候可以直接使用
state._query = _query
state.page.pageIndex = 1 // 顯示第一頁
// 統計總數
help.selectCount(listInfo.tableName, _query).then((count) => {
// 設置分頁
state.page.pageTotal = count
})
// 獲取數據
help.select(listInfo.tableName, listInfo.listCol, _query, state.page).then((data) => {
_dataList.length = 0
_dataList.push(...data)
})
})
// 翻頁,依據緩存的查詢條件,獲取其他頁號的數據
watch(() => state.page.pageIndex, () => {
// 獲取數據
help.select(listInfo.tableName, listInfo.listCol, state._query, state.page).then((data) => {
_dataList.length = 0
_dataList.push(...data)
})
})
return state
}
return {
regDataListState, // 父組件注冊狀態
dataList: _dataList, // 父組件獲得列表
dataListState // 子組件獲得狀態
}
}
雖然代碼多了一點,但是這里處理好各種需求,組件里面就可以輕松使用了。
-
讀取配置信息 blogListInfo
因為博文列表、討論列表需要的信息都是不一樣的,所以不同的信息都放在了一個json文件里面,這里用了vite2 的 import 方式讀取,然后按照參數(jsonFlag)獲取對應的信息。 -
VueDS.useStore
獲取輕量級狀態的注冊等函數。 -
子組件里面獲取狀態
因為 vue 的 inject 必須在 vue 的 setup 的進程里面才可以獲取,而在事件的進程里面無法獲取,所以只好在這里先把需要的狀態獲取出來,如果是父組件的話,當然取不出來。 -
regDataListState
父組件注冊局部狀態的函數。從配置信息里面提取對應的信息,設置給 find、findKind、query。 -
監聽 state.isReload
isReload 主要針對查詢需求,設置好查詢條件后對 isReload 取反,就會觸發watch,然后這里執行獲取數據的操作。在線演示用的是webSQL,正式項目可以使用 axios 向后端申請數據。然后獲取數據設置給 dataList。
這里需要統計總記錄數,而下面的翻頁事件里就不需要統計總記錄數了。 -
監聽 page.pageIndex
這個是應對翻頁的需求的。分成兩個來監聽,目的是區分要不要統計總記錄數。
如果數據量不大的話,統計總數沒啥問題,每次翻頁都統計一下用戶也不會有啥感覺。
但是如果數據量大的話,還是每次翻頁都去統計一下總數,那么就太浪費性能了。
所以這里做了一下區分,翻頁的時候不統計總數,重置查詢條件的時候才會統計總數。
父組件的使用方法
我們先來看一個簡單情況,討論列表的使用方式:
<template>
<el-card shadow="hover"
v-for="(item, index) in dataList"
:key="'discusslist_' + index"
:id="'discuss_'+ item.ID"
>
<template #header>
<div class="card-header">
{{item.discusser}}
<span class="button">({{dateFormat(item.addTime).format('YYYY-MM-DD')}})</span>
</div>
</template>
<!--簡介-->
<div class="text item" v-html="item.concent" :id="'discuss1_'+ item.ID"></div>
<hr>
<i class="el-icon-circle-check"></i> {{item.agreeCount}}
</el-card>
<!--沒有找到數據-->
<el-empty description="沒有討論呢,搶個沙發唄。" v-if="dataList.length === 0"></el-empty>
<!--分頁-->
<pager/>
<!--討論表單-->
<discussForm :id="id"/>
</template>
把分頁和討論的表單都分布出去做成了單獨的組件,這樣模板里面可以專注討論列表的設置了。
集中目標不分心。
// 組件
import { ref } from 'vue'
// 組件
import discussForm from '../components/discuss-form.vue'
import pager from '../components/pager.vue'
// 數據列表的狀態
import dataListControl from '../control/data-list'
// 日期格式化
const dateFormat = dayjs
// 組件的屬性,獲取博文ID
const props = defineProps({
id: String
})
// 獲取注冊函數和數據容器,
const { regDataListState, dataList } = dataListControl('discussList')
// 注冊狀態,設置博文ID為查詢條件,獲取博文的討論數據。
const discussState = regDataListState()
discussState.find.blogId = props.id
discussState.isReload = !discussState.isReload
怎么樣?是不是很簡潔。
-
父組件里面使用
首先引入 control/data-list,然后獲取狀態,根據需求設置好查詢條件。
最后別忘了使用 dataList 綁定模板。 -
分頁控件使用
分頁做成了單獨且可以共享的組件,在組件里面可以直接獲取局部狀態,給 el-pagination 設置屬性,這樣就不需要父組件操心了。
/src/components/pager.vue
<template>
<el-pagination
background
layout="prev, pager, next"
v-model:currentPage="dataListState.page.pageIndex"
:page-size="dataListState.page.pageSize"
:total="dataListState.page.pageTotal">
</el-pagination>
</template>
獲取狀態,綁定模板。
// 統一的數據列表的分頁組件
import { defineProps } from 'vue'
// 數據列表的狀態
import dataListControl from '../control/data-list'
// 獲取列表的局部狀態
const { dataListState } = dataListControl()
翻頁的時候頁號會變化,觸發 watch 的監聽,從而實現翻頁獲取數據的效果。
子組件的使用方法
也是一樣的步驟,只是不需要注冊,而是獲取父組件注冊的狀態,得到狀態后,在需要的地方修改即可。
這樣組件里面的代碼就非常簡單了。比如上面那個分頁組件。
我們來看一下討論表單的組件,模板部分就是一個普通的表單,跳過直接看js部分:
import { reactive, watch } from 'vue'
// 數據列表的狀態
import dataListControl from '../control/data-list'
// 表單管理
import discussFromControl from '../control/data-form'
// 獲取討論列表的狀態
const { dataListState } = dataListControl('discussList')
// 表單管理
const { discussModel, addNewDiscuss } = discussFromControl()
// 日期格式化
const dateFormat = dayjs
// 組件的屬性
const props = defineProps({
id: String
})
// 發布討論
const submit = () => {
discussModel.blogId = props.id
addNewDiscuss().then((id) => {
// 通知列表
dataListState.isReload = !dataListState.isReload
})
}
先獲取討論列表的狀態,然后發布討論成功后,調用討論列表的狀態,從而觸發討論列表重新加載討論數據。
其他代碼就不一一介紹了,感興趣的話可以到 gitee 看源碼。
輕量級狀態 vue-data-state
輕量級狀態已經發布到 npm ,可以使用yarn add vue-data-state
來安裝。
源碼
-
輕量級狀態 vue-data-state
https://gitee.com/naturefw/vue-data-state