使用局部狀態(輕量級狀態)優化博客代碼


上兩篇介紹了如何用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>&nbsp;{{item.agreeCount}}&nbsp;&nbsp;
  </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 來安裝。

源碼

在線演示

狀態
https://naturefw.gitee.io/vue-data-state/

博客
https://naturefw.gitee.io/vue3-blog


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM