Vue3 練手項目,爲了加深對 composition-api
的理解,項目參考於 sl1673495/vue-bookshelf,不過這個項目仍是基於 vue2+composition-api
,裏面對於組合函數的使用和理解仍是頗有幫助的,這裏用 Vue3
作了修改。html
項目地址:vue-bookshelfvue
項目中會用到的 Vue3 api,你須要在開始以前對它們有所瞭解:node
Vue3 中新增的一對api,provide
和 inject
,能夠很方便的管理應用的全局狀態,有興趣能夠參考下這篇文章:Vue 3 store without Vuexreact
官方文檔對 Provide / Inject
的使用說明:Provide / Injectgit
利用這兩個api,在沒有vuex的狀況下也能夠很好的管理項目中的全局狀態:github
import { provide, inject } from 'vue' const ThemeSymbol = Symbol() const Ancestor = { setup() { provide(ThemeSymbol, 'dark') } } const Descendent = { setup() { const theme = inject(ThemeSymbol, 'light' /* optional default value */) return { theme } } }
項目很簡單,主要邏輯以下:vue-router
項目基於 vue-cli
搭建:vuex
項目基於 Provide/Inject
實現全局的圖書狀態管理,context/books.ts
包含兩個組合函數:vue-cli
useBookListProvide
提供書籍的全局狀態管理和方法useBookListInject
書籍狀態和方法注入(在須要的組件中使用)在main.ts中,根組件注入全局狀態:typescript
// main.ts import { createApp, h } from 'vue' import App from './App.vue' import { useBookListProvide } from '@/context' const app = createApp({ setup() { useBookListProvide(); return () => h(App) } })
組件中使用:
import { defineComponent } from "vue"; import { useBookListInject } from "@/context"; import { useAsync } from "@/hooks"; import { getBooks } from "@/hacks/fetch"; export default defineComponent({ name: "books", setup() { // 注入全局狀態 const { setBooks, booksAvaluable } = useBookListInject(); // 獲取數據的異步組合函數 const loading = useAsync(async () => { const requestBooks = await getBooks(); setBooks(requestBooks); }); return { booksAvaluable, loading, }; } });
組合函數 useAsync
目的是管理異步方法先後loading狀態:
import { onMounted, ref } from 'vue' export const useAsync = (func: () => Promise<any>) => { const loading = ref(false) onMounted(async () => { try { loading.value = true await func() } catch (error) { throw error } finally { loading.value = false } }) return loading }
組件中使用:
<Books :books="booksAvaluable" :loading="loading"></Books>
對於分頁這裏使用組合函數 usePages
進行管理,目的是返回當前頁的圖書列表和分頁組件所需的參數:
import { reactive, Ref, ref, watch } from 'vue' export interface PageOption { pageSize?: number } export function usePages<T>(watchCallback: () => T[], pageOption?: PageOption) { const { pageSize = 10 } = pageOption || {} const rawData = ref([]) as Ref<T[]> const data = ref([]) as Ref<T[]> const bindings = reactive({ current: 1, currentChange: (currentPage: number) => { data.value = sliceData(rawData.value, currentPage) }, }) const sliceData = (rawData: T[], currentPage: number) => { return rawData.slice((currentPage - 1) * pageSize, currentPage * pageSize) } watch( watchCallback, (value) => { rawData.value = value bindings.currentChange(1) }, { immediate: true, } ) return { data, bindings, } }
基於 composition-api
能夠很方便的將統一的邏輯進行拆分,例如分頁塊的邏輯,極可能在其它的業務模塊中使用,因此統一拆分到了hooks
文件夾下。
這裏簡單實現了分頁插件,參考 element-plus/pagination 的分頁組件。
<Pagination class="pagination" :total="books.length" :page-size="pageSize" :hide-on-single-page="true" v-model:current-page="bindings.current" @current-change="bindings.currentChange" />
Vue3 能夠實如今組件上使用多個 v-model
進行雙向數據綁定,讓 v-model
的使用更加靈活,詳情可查看官方文檔 v-model。
項目中的分頁組件也使用了v-model:current-page
的方式進行傳參。
vue3 的指令也作了更新: 官方文檔-directives
主要是生命週期函數的變化:
const MyDirective = { beforeMount(el, binding, vnode, prevVnode) {}, mounted() {}, beforeUpdate() {}, // new updated() {}, beforeUnmount() {}, // new unmounted() {} }
項目中的指令主要是針對圖片src作處理,directives/load-img-src.ts
:
// 圖片加載指令,使用 ![](默認路徑) // 圖片加載失敗路徑 const errorURL = 'https://imgservices-1252317822.image.myqcloud.com/image/20201015/45prvdakqe.svg' const loadImgSrc = { beforeMount(el: HTMLImageElement, binding: { value: string }) { const imgURL = binding.value || '' const img = new Image() img.src = imgURL img.onload = () => { if (img.complete) { el.src = imgURL } } img.onerror = () => (el.src = errorURL) }, }