摘要
在日常的前端开发中,框架总是一波接着一波的出现。为了不落后在这个快节奏的更新中,学习框架要抓住重点。
这个框架是干什么的
uniapp
是 dcloud 出品的一个跨越多端的前端框架,基于vue
开发。使得用户可以编写vue
一套代码部署在微信小程序、支付宝小程序、钉钉、app 和网页端。怎么听起来很方便吧。但是多端开发肯定是没有原生开发的更加贴合终端机器了。
框架给我们解决了差异性,但是也有和平常的 vue 不同,例如div
和ul
和li
等改为view
,body
的元素选择器请改为page
,同样,span
和font
改为text
、a
改为navigator
、img
改为image
因为要考虑到原生渲染,小程序等情况。这个查阅官方文档即可.
基础框架的开发配置
这里就是核心重点,因为一个框架我们都是要拿来做业务开发的。那么前端业务中,就包含了 3 个(具体看框架和场景)方面。
- UI 组件库(构建页面的基础)
- 状态管理(pinia 类似的),
- 数据交互(接口请求)
下面就这对 uniapp 来对这三块基础做封装介绍。
UI 组件库
这个具体去查找你所喜欢的组件库,但是一定要 注意版本号 我这里使用的是 vue3+ts。所以我使用的组件库是wot-design-uni
一个基于 vue3 开发的 uniapp 组件库。
那么接下来的安装配置才是重点,也帮助我们了解 uniapp 的配置,其他的大同小异。
安装
安装分为 2 种,uni_modules 安装、npm 安装。
有的组件估计有其他的种类,不重要,上面的意思是一个使用uni-app插件市场
选择使用HBuildX
导入。
不用HBuildX
那么就使用前端熟悉的npm
安装
其他的包安装对应的 UI 组件都有介绍,这里就不废话了。
配置
这里是第一步 npm 安装之后需要做的事情。
传统 vue 组件,需要安装、引用、注册,三个步骤后才能使用组件。easycom 将其精简为一步。只要组件路径符合规范(具体见easycom),就可以不用引用、注册,直接在页面中使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| { "easycom": { "autoscan": true, "custom": { "^wd-(.*)": "wot-design-uni/components/wd-$1/wd-$1.vue" } },
"pages": [ ] }
|
这里的配置是很重要的,通常的组件库中会给出对应的设置代码。所以不用记忆什么。
当然也会有其他的引入方式,例如 vite 常用的自动导入,这里就不过多的探讨。
上面就可以完成 UI 库的引入和使用了。
状态管理
通常的一个程序开发,一些数据我们都会存在本地,不然的话一刷新,什么用户信息都没了,这不合适吧。
这里我使用的是pinia
。
1
| pnpm install pinia pinia-plugin-persistedstate
|
然后就像我们在开发 vue 项目一样
新建src/store/user.ts
键入如下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| import { defineStore } from "pinia"; import { ref } from "vue";
const initState = { nickname: "", avatar: "" };
export const useUserStore = defineStore( "user", () => { const userInfo = ref<IUserInfo>({ ...initState });
const setUserInfo = (val: IUserInfo) => { userInfo.value = val; };
const clearUserInfo = () => { userInfo.value = { ...initState }; }; const reset = () => { userInfo.value = { ...initState }; }; const isLogined = computed(() => !!userInfo.value.token);
return { userInfo, setUserInfo, clearUserInfo, isLogined, reset, }; }, { persist: true, } );
|
那么在浏览器中,持久化都是localStorage
、或者sessionStorage
。那么微信中,使用的是**wx.setStorage
** 这样的方法。显然这里的持久化需要改一下,改成 uniapp 的,这样才能多端都一样。
新建src/store/index.ts
,输入下面的内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| import { createPinia } from "pinia"; import { createPersistedState } from "pinia-plugin-persistedstate";
const store = createPinia(); store.use( createPersistedState({ storage: { getItem: uni.getStorageSync, setItem: uni.setStorageSync, }, }) );
export default store;
export * from "./user";
|
随后在 main.ts 中导出注册。
上面的重点就是数据持久化,主要是使用了uni
的一些 API。
数据交互
页面编写好了,总要和后台的接口进行交互吧。那么前端开发中会用到请求接口的网络请求库。这里由于是 uniapp,当然使用 uniapp 给的请求库,这样就完成了多端统一。
使用网络请求库,我们总要考虑如下的事情
- 可配置地址(服务器如果换了,或者域名更新。只需要改变地址就可以了,不需要重新修改接口)
- 请求头上的身份(token 之类的)
- 请求头上挂载当前客户端的类型(可选,我这里是有需要)
- 接口请求的封装
- 接口返回的数据类型
- 接口错误代码的处理。
拦截封装实现
前面三步是通过拦截实现的,这里的拦截就和你编写 axios 的拦截一样。uniapp 当然也有自己的拦截。
uni.addInterceptor(’类型’,回调方法)
新建src/interceptors/request.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73
| import qs from "qs"; import { useUserStore } from "@/store"; import { platform } from "@/utils/platform"; import { getEvnBaseUrl } from "@/utils";
export type CustomRequestOptions = UniApp.RequestOptions & { query?: Record<string, any>; hideErrorToast?: boolean; } & IUniUploadFileOptions;
const baseUrl = getEvnBaseUrl();
const httpInterceptor = { invoke(options: CustomRequestOptions) { if (options.query) { const queryStr = qs.stringify(options.query); if (options.url.includes("?")) { options.url += `&${queryStr}`; } else { options.url += `?${queryStr}`; } } if (!options.url.startsWith("http")) { if (JSON.parse(__VITE_APP_PROXY__)) { } else { options.url = baseUrl + options.url; } options.url = baseUrl + options.url; } options.timeout = 10000; options.header = { platform, ...options.header, }; const userStore = useUserStore(); const { token } = userStore.userInfo as unknown as IUserInfo; if (token) { options.header.Authorization = `Bearer ${token}`; } }, };
export const requestInterceptor = { install() { uni.addInterceptor("request", httpInterceptor); uni.addInterceptor("uploadFile", httpInterceptor); }, };
|
我这里还引入了第三方包,qs 可以分解组合 URL 字符串的参数。
1 2
| import { platform } from "@/utils/platform"; import { getEvnBaseUrl } from "@/utils";
|
这 2 个导入不重要,因为我后面会告诉你如何获取吧。
上面的主要内容就是uni.addInterceptor
然后就是回调函数中的invoke
上面的文件内容就是完成了 request 请求和 uploadFile 文件上传请求的拦截。当然了,看我新建的目录就知道会有其他的拦截使用方法是一样的。
后面统一在src/interceptors/index.ts
中导出
1
| export { requestInterceptor } from "./request";
|
同样的也在 main.ts 中导入注册
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| import { createSSRApp } from "vue"; import App from "./App.vue"; import store from "./store"; import { requestInterceptor } from "./interceptors"; import "virtual:uno.css"; import "@/style/index.scss";
export function createApp() { const app = createSSRApp(App); app.use(store); app.use(requestInterceptor); return { app, }; }
|
完成了接口的拦截封装,那么还剩下接口请求的封装了。
接口请求封装
通过一个函数return Promise
来实现对uni.request
异步接口调用,来同步获取结果。
新建utils/http.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
|
export const http = <T,>(options: CustomRequestOptions) => { return new Promise<IResData<T>>((resolve, reject) => { uni.request({ ...options, dataType: "json", responseType: "json", success(res) { if (res.statusCode >= 200 && res.statusCode < 300) { resolve(res.data as IResData<T>); } else if (res.statusCode === 401) { reject(res); } else { !options.hideErrorToast && uni.showToast({ icon: "none", title: (res.data as IResData<T>).msg || "请求错误", }); reject(res); } }, fail(err) { uni.showToast({ icon: "none", title: "网络错误,换个网络试试", }); reject(err); }, }); }); };
|
这样的话就可以指定结果类型了。
1 2 3 4 5 6 7 8 9
| <script setup lang="ts"> const getData = async () => { let result = await http<string[]>({ url, query, method: 'GET', }) } </script>
|
总结
这样的话,对 uniapp 进行了一层基础框架的构建。使得项目开发更加的结构化
这样是不是很繁琐呢,这样还是基础。够用,但是可以做的更好。所以我们可以使用别人构建好的框架,里面包含了我上面提及的基础点,也还有 css,路由拦截等各种插件。这个是unibest基础框架,上面的分享主要是记录如果我们自己构建 uniapp 框架,是如何思考并解决问题的。