ブログ機能と軽量状態管理の統合
前回までの記事では、Vite2 + Vue3でブログサイトを構築する方法と、軽量状態管理の基本的な使い方について解説しました。今回は、それらを組み合わせてブログのリスト機能を効率化する方法を紹介します。
共通するリスト機能の抽象化
ブログには、記事一覧、編集画面の記事リスト、コメントリストといった複数のリストが存在します。これらは構造が類似しており、共通部分を抽出して再利用可能な関数として設計できます。
- 検索条件
- ページネーション
- データ格納領域
この共通性を活かすために、軽量状態管理ライブラリの「局所状態」を活用します。
リスト機能用の局所状態の定義
// store-ds/index.js
import VueDataState from 'vue-data-state'
export default VueDataState.createStore({
global: {
currentUser: {
name: 'taro'
}
},
local: {
listState() {
return {
searchType: {},
searchValue: {},
pagination: {
total: 100,
size: 5,
current: 1,
sort: { id: false }
},
cachedQuery: {},
refreshFlag: false
}
}
},
init(state) {
// 初期化処理
}
})
listState:複数のコンポーネントで再利用可能なリスト状態searchType:検索条件の種別を格納するコンテナsearchValue:ユーザー入力の検索キーワードcachedQuery:検索条件のキャッシュ
MVCスタイルのコントローラ実装
// control/list-controller.js
import { watch, reactive } from 'vue'
import { useStore } from 'vue-data-state'
import { webSQL } from 'nf-web-storage'
import config from '/config/list.config.json'
export default function listController(flag) {
const listData = reactive([])
const { db } = webSQL.useDB()
const { register, retrieve } = useStore()
const listState = retrieve.listState()
const registerState = () => {
const state = register.listState()
const settings = config[flag]
if (!settings) return state
state.searchValue = settings.searchValue
state.searchType = settings.searchType
state.pagination = settings.pagination
watch(() => state.refreshFlag, () => {
const query = {}
for (const key in state.searchValue) {
const value = state.searchValue[key]
const type = state.searchType[key]
if (value && value.length > 0) {
query[key] = [type, value]
}
}
state.cachedQuery = query
state.pagination.current = 1
db.count(settings.table, query).then(count => {
state.pagination.total = count
})
db.fetch(settings.table, settings.columns, query, state.pagination)
.then(data => {
listData.length = 0
listData.push(...data)
})
})
watch(() => state.pagination.current, () => {
db.fetch(settings.table, settings.columns, state.cachedQuery, state.pagination)
.then(data => {
listData.length = 0
listData.push(...data)
})
})
return state
}
return {
registerState,
listData,
listState
}
}
親コンポーネントでの使用例
// components/comment-list.vue
import { defineProps } from 'vue'
import listController from '../control/list-controller'
import CommentForm from './comment-form.vue'
import Pagination from './pagination.vue'
const props = defineProps({
articleId: String
})
const { registerState, listData } = listController('commentList')
const commentState = registerState()
commentState.searchValue.articleId = props.articleId
commentState.refreshFlag = !commentState.refreshFlag
ページネーションコンポーネント
// components/pagination.vue
<template>
</template>
<script>
import { useStore } from 'vue-data-state'
import listController from '../control/list-controller'
const { listState } = listController()
</script>
子コンポーネントでの状態利用
// components/comment-form.vue
import { reactive } from 'vue'
import listController from '../control/list-controller'
import commentController from '../control/comment-form.js'
const { listState } = listController('commentList')
const { commentData, submitComment } = commentController()
const props = defineProps({
articleId: String
})
const postComment = () => {
commentData.articleId = props.articleId
submitComment().then(() => {
listState.refreshFlag = !listState.refreshFlag
})
}