前言
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,provide
和 inject
,能够很不便的治理利用的全局状态,有趣味能够参考下这篇文章: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.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)
},
}