前言

Vue3 练手我的项目,为了加深对 composition-api 的了解,我的项目参考于 sl1673495/vue-bookshelf,不过这个我的项目还是基于 vue2+composition-api,外面对于组合函数的应用和了解还是很有帮忙的,这里用 Vue3 做了批改。

我的项目地址:vue-bookshelf

我的项目中会用到的 Vue3 api,你须要在开始之前对它们有所理解:

  • [x] Provide / Inject
  • [x] ref、reactive、watch、computed
  • [x] directive
  • [x] 生命周期函数
  • [x] v-model 多选项绑定

provide/inject代替vuex

Vue3 中新增的一对api,provideinject,能够很不便的治理利用的全局状态,有趣味能够参考下这篇文章:Vue 3 store without Vuex

官网文档对 Provide / Inject 的应用阐明:Provide / Inject

利用这两个api,在没有vuex的状况下也能够很好的治理我的项目中的全局状态:

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-cli 搭建:

  • typescript
  • vue3
  • vue-router
  • sass

context

我的项目基于 Provide/Inject 实现全局的图书状态治理,context/books.ts蕴含两个组合函数:

  • useBookListProvide 提供书籍的全局状态治理和办法
  • useBookListInject 书籍状态和办法注入(在须要的组件中应用)

在main.ts中,根组件注入全局状态:

// main.tsimport { 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)  },}