第241集:Vue 3 Uni-app框架深度使用指南
概述
Uni-app是一个使用Vue.js开发所有前端应用的框架,开发者编写一套代码,可发布到iOS、Android、Web(响应式)、以及各种小程序(微信/支付宝/百度/头条/QQ/钉钉/淘宝)、快应用等多个平台。本集将深入探讨Uni-app框架的核心概念、项目结构、组件开发、API调用以及跨平台适配等内容,帮助你掌握Uni-app的深度使用技巧。
一、Uni-app框架核心概念
1.1 Uni-app的设计理念
- 一套代码,多端运行:通过条件编译和平台特有API适配,实现跨平台开发
- Vue语法兼容:支持Vue 2和Vue 3语法,降低学习成本
- 组件化开发:采用组件化思想,提高代码复用性
- 原生能力调用:通过Uni-app API可以调用各平台的原生能力
- 性能优化:针对不同平台进行了性能优化,提供流畅的用户体验
1.2 Uni-app支持的平台
- 移动端:iOS、Android
- Web端:H5响应式网页
- 小程序:微信小程序、支付宝小程序、百度小程序、头条小程序、QQ小程序、钉钉小程序、淘宝小程序
- 快应用:华为快应用、小米快应用等
1.3 Uni-app与其他跨平台框架对比
| 框架 | 特点 | 适用场景 |
|---|---|---|
| Uni-app | 一套代码多端运行、Vue语法、丰富的组件库 | 需要覆盖多个平台的应用 |
| Flutter | 原生性能、Dart语言、丰富的UI组件 | 注重性能和UI一致性的应用 |
| React Native | JavaScript语言、原生组件、社区活跃 | 熟悉React生态的开发者 |
| Taro | React/Vue语法、多端编译、小程序优先 | 小程序开发为主的应用 |
二、Uni-app环境搭建与项目创建
2.1 开发环境配置
- 安装Node.js:确保Node.js版本>=12.0.0
- 安装HBuilderX:Uni-app官方推荐的IDE,提供完整的开发体验
- 安装微信开发者工具:用于微信小程序预览和调试
- 安装Android Studio(可选):用于Android应用调试
- 安装Xcode(可选,Mac):用于iOS应用调试
2.2 使用HBuilderX创建Uni-app项目
- 打开HBuilderX,点击"文件" -> "新建" -> "项目"
- 选择"Uni-app",填写项目名称和路径
- 选择模板(默认模板、空模板、Hello uni-app等)
- 选择Vue版本(Vue 3推荐)
- 点击"创建"按钮
2.3 使用命令行创建Uni-app项目
# 全局安装create-uniapp-cli
npm install -g @dcloudio/vue-cli
# 创建Uni-app项目
vue create -p dcloudio/uni-preset-vue my-uniapp-project
# 进入项目目录
cd my-uniapp-project
# 启动开发服务器
npm run dev:h5三、Uni-app项目结构分析
3.1 基本项目结构
┌─uni_modules/ # Uni-app插件市场模块
├─pages/ # 页面目录
│ └─index/ # 首页
│ ├─index.vue # 首页组件
│ └─index.json # 页面配置
├─static/ # 静态资源目录
├─uniCloud/ # 云开发目录
├─components/ # 自定义组件目录
├─common/ # 公共资源目录
├─store/ # 状态管理目录
├─App.vue # 应用入口组件
├─main.js # 应用入口文件
├─manifest.json # 应用配置文件
├─pages.json # 页面路由配置
├─uni.scss # Uni-app全局样式
└─package.json # 项目依赖配置3.2 核心配置文件详解
3.2.1 pages.json
{
"pages": [
{
"path": "pages/index/index",
"style": {
"navigationBarTitleText": "首页",
"navigationBarBackgroundColor": "#ffffff",
"navigationBarTextStyle": "black"
}
},
{
"path": "pages/detail/detail",
"style": {
"navigationBarTitleText": "详情页"
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarBackgroundColor": "#ffffff",
"backgroundColor": "#f5f5f5"
},
"tabBar": {
"color": "#999999",
"selectedColor": "#007aff",
"backgroundColor": "#ffffff",
"list": [
{
"pagePath": "pages/index/index",
"text": "首页",
"iconPath": "static/home.png",
"selectedIconPath": "static/home-active.png"
},
{
"pagePath": "pages/mine/mine",
"text": "我的",
"iconPath": "static/mine.png",
"selectedIconPath": "static/mine-active.png"
}
]
}
}3.2.2 manifest.json
{
"name": "my-uniapp",
"appid": "__UNI__XXXXXX",
"description": "Uni-app示例应用",
"versionName": "1.0.0",
"versionCode": 1,
"transformPx": true,
"mp-weixin": {
"appid": "wx1234567890abcdef",
"setting": {
"urlCheck": false
},
"usingComponents": true
},
"h5": {
"title": "Uni-app H5",
"router": {
"base": "/",
"mode": "history"
}
},
"app-plus": {
"usingComponents": true,
"compatible": {
"ignoreVersion": true
},
"distribute": {
"android": {
"packageName": "com.example.myapp",
"icons": {
"hdpi": "static/icon.png"
}
},
"ios": {
"bundleId": "com.example.myapp",
"icons": {
"appstore": "static/icon.png"
}
}
}
}
}四、Uni-app页面开发
4.1 页面生命周期
<template>
<view class="page-container">
<text>页面内容</text>
</view>
</template>
<script setup>
import { onMounted, onUnmounted } from 'vue';
// Vue 3 生命周期
onMounted(() => {
console.log('Vue onMounted');
});
onUnmounted(() => {
console.log('Vue onUnmounted');
});
// Uni-app 页面生命周期
import { onLoad, onShow, onHide, onUnload, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';
onLoad((options) => {
console.log('页面加载', options);
});
onShow(() => {
console.log('页面显示');
});
onHide(() => {
console.log('页面隐藏');
});
onUnload(() => {
console.log('页面卸载');
});
onPullDownRefresh(() => {
console.log('下拉刷新');
// 停止下拉刷新
uni.stopPullDownRefresh();
});
onReachBottom(() => {
console.log('上拉触底');
});
</script>4.2 页面路由与跳转
4.2.1 声明式导航
<!-- 普通跳转 -->
<navigator url="/pages/detail/detail" open-type="navigate">
跳转到详情页
</navigator>
<!-- 带参数跳转 -->
<navigator url="/pages/detail/detail?id=1&name=test" open-type="navigate">
跳转到详情页
</navigator>
<!-- 重定向 -->
<navigator url="/pages/login/login" open-type="redirect">
重定向到登录页
</navigator>
<!-- 切换Tab -->
<navigator url="/pages/mine/mine" open-type="switchTab">
切换到我的页面
</navigator>4.2.2 编程式导航
// 普通跳转
uni.navigateTo({
url: '/pages/detail/detail?id=1&name=test',
success: () => {
console.log('跳转成功');
}
});
// 重定向
uni.redirectTo({
url: '/pages/login/login'
});
// 切换Tab
uni.switchTab({
url: '/pages/mine/mine'
});
// 关闭所有页面,打开到应用内的某个页面
uni.reLaunch({
url: '/pages/index/index'
});
// 关闭当前页面,返回上一页面或多级页面
uni.navigateBack({
delta: 1
});4.3 页面参数传递与接收
4.3.1 传递参数
// 方式1:URL参数
uni.navigateTo({
url: `/pages/detail/detail?id=${id}&name=${name}`
});
// 方式2:事件通道
uni.navigateTo({
url: '/pages/detail/detail',
events: {
// 监听被打开页面发送的事件
acceptDataFromOpenedPage: (data) => {
console.log('Received data:', data);
}
},
success: (res) => {
// 向被打开页面发送数据
res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' });
}
});4.3.2 接收参数
// 方式1:onLoad接收URL参数
onLoad((options) => {
console.log('Received params:', options);
// options.id, options.name
});
// 方式2:事件通道接收数据
onLoad(() => {
const eventChannel = uni.getOpenerEventChannel();
// 监听从父页面发送的数据
eventChannel.on('acceptDataFromOpenerPage', (data) => {
console.log('Received data from opener:', data);
});
// 向父页面发送数据
eventChannel.emit('acceptDataFromOpenedPage', { data: 'response' });
});五、Uni-app组件开发
5.1 自定义组件基础
5.1.1 创建自定义组件
<!-- components/custom-button/custom-button.vue -->
<template>
<button
class="custom-button"
:class="{ 'custom-button--primary': type === 'primary' }"
@click="handleClick"
>
<slot></slot>
</button>
</template>
<script setup>
// 组件属性
const props = defineProps({
type: {
type: String,
default: 'default'
}
});
// 组件事件
const emit = defineEmits(['click']);
const handleClick = () => {
emit('click');
};
</script>
<style scoped>
.custom-button {
padding: 10px 20px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #fff;
}
.custom-button--primary {
background-color: #007aff;
color: #fff;
border-color: #007aff;
}
</style>5.1.2 使用自定义组件
<template>
<view class="container">
<custom-button @click="handleButtonClick">
默认按钮
</custom-button>
<custom-button type="primary" @click="handleButtonClick">
主要按钮
</custom-button>
</view>
</template>
<script setup>
// 引入自定义组件
import customButton from '@/components/custom-button/custom-button.vue';
const handleButtonClick = () => {
console.log('Button clicked');
};
</script>5.2 全局组件注册
// main.js
import { createSSRApp } from 'vue';
import App from './App.vue';
import customButton from './components/custom-button/custom-button.vue';
export function createApp() {
const app = createSSRApp(App);
// 注册全局组件
app.component('custom-button', customButton);
return {
app
};
}5.3 组件通信
5.3.1 Props与事件
<!-- 父组件 -->
<template>
<child-component
:message="parentMessage"
@update:message="parentMessage = $event"
></child-component>
</template>
<script setup>
import { ref } from 'vue';
import childComponent from './child-component.vue';
const parentMessage = ref('Hello from parent');
</script>
<!-- 子组件 -->
<template>
<view>
<text>{{ message }}</text>
<button @click="updateMessage">更新消息</button>
</view>
</template>
<script setup>
const props = defineProps(['message']);
const emit = defineEmits(['update:message']);
const updateMessage = () => {
emit('update:message', 'Hello from child');
};
</script>5.3.2 依赖注入(Provide/Inject)
<!-- 父组件 -->
<template>
<child-component></child-component>
</template>
<script setup>
import { provide } from 'vue';
import childComponent from './child-component.vue';
// 提供数据
provide('userInfo', {
name: '张三',
age: 25
});
</script>
<!-- 子组件 -->
<template>
<view>
<text>{{ userInfo.name }}</text>
</view>
</template>
<script setup>
import { inject } from 'vue';
// 注入数据
const userInfo = inject('userInfo');
</script>六、Uni-app API调用
6.1 网络请求
// 发起GET请求
uni.request({
url: 'https://api.example.com/users',
method: 'GET',
data: {
page: 1,
pageSize: 10
},
success: (res) => {
console.log('Request success:', res.data);
},
fail: (err) => {
console.error('Request failed:', err);
}
});
// 发起POST请求
uni.request({
url: 'https://api.example.com/users',
method: 'POST',
data: {
name: '张三',
age: 25
},
header: {
'Content-Type': 'application/json'
},
success: (res) => {
console.log('Request success:', res.data);
}
});
// 上传文件
uni.uploadFile({
url: 'https://api.example.com/upload',
filePath: tempFilePath,
name: 'file',
formData: {
'user': 'test'
},
success: (res) => {
console.log('Upload success:', res.data);
}
});
// 下载文件
uni.downloadFile({
url: 'https://example.com/file.pdf',
success: (res) => {
if (res.statusCode === 200) {
console.log('Download success:', res.tempFilePath);
}
}
});6.2 本地存储
// 设置存储
uni.setStorage({
key: 'userInfo',
data: {
name: '张三',
age: 25
},
success: () => {
console.log('Storage set successfully');
}
});
// 获取存储
uni.getStorage({
key: 'userInfo',
success: (res) => {
console.log('Storage get successfully:', res.data);
}
});
// 移除存储
uni.removeStorage({
key: 'userInfo',
success: () => {
console.log('Storage removed successfully');
}
});
// 清空存储
uni.clearStorage({
success: () => {
console.log('Storage cleared successfully');
}
});6.3 界面交互
// 显示提示框
uni.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
});
// 显示加载框
uni.showLoading({
title: '加载中...'
});
// 隐藏加载框
uni.hideLoading();
// 显示模态框
uni.showModal({
title: '提示',
content: '确定要执行此操作吗?',
success: (res) => {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
// 显示操作菜单
uni.showActionSheet({
itemList: ['选项1', '选项2', '选项3'],
success: (res) => {
console.log('用户选择了', res.tapIndex);
}
});6.4 设备API
// 获取系统信息
uni.getSystemInfo({
success: (res) => {
console.log('System info:', res);
// res.platform: 平台信息
// res.screenWidth: 屏幕宽度
// res.screenHeight: 屏幕高度
// res.model: 设备型号
// res.version: 操作系统版本
}
});
// 获取网络状态
uni.getNetworkType({
success: (res) => {
console.log('Network type:', res.networkType);
}
});
// 监听网络状态变化
uni.onNetworkStatusChange((res) => {
console.log('Network status changed:', res.isConnected, res.networkType);
});
// 获取位置信息
uni.getLocation({
type: 'gcj02',
success: (res) => {
console.log('Location:', res.latitude, res.longitude);
}
});七、Uni-app状态管理
7.1 使用Pinia进行状态管理
7.1.1 安装Pinia
npm install pinia7.1.2 配置Pinia
// main.js
import { createSSRApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
export function createApp() {
const app = createSSRApp(App);
const pinia = createPinia();
app.use(pinia);
return {
app
};
}7.1.3 创建Store
// store/user.js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null,
token: ''
}),
getters: {
isLoggedIn: (state) => !!state.token,
userName: (state) => state.userInfo?.name || ''
},
actions: {
login(userInfo, token) {
this.userInfo = userInfo;
this.token = token;
// 保存到本地存储
uni.setStorageSync('userInfo', userInfo);
uni.setStorageSync('token', token);
},
logout() {
this.userInfo = null;
this.token = '';
// 清除本地存储
uni.removeStorageSync('userInfo');
uni.removeStorageSync('token');
},
init() {
// 从本地存储恢复状态
this.userInfo = uni.getStorageSync('userInfo');
this.token = uni.getStorageSync('token');
}
}
});7.1.4 使用Store
<template>
<view class="container">
<text v-if="userStore.isLoggedIn">欢迎,{{ userStore.userName }}</text>
<button v-else @click="handleLogin">登录</button>
<button v-if="userStore.isLoggedIn" @click="handleLogout">退出</button>
</view>
</template>
<script setup>
import { useUserStore } from '@/store/user';
const userStore = useUserStore();
// 初始化状态
userStore.init();
const handleLogin = () => {
userStore.login(
{ name: '张三', age: 25 },
'token123456'
);
};
const handleLogout = () => {
userStore.logout();
};
</script>八、Uni-app跨平台适配
8.1 条件编译
条件编译是Uni-app的核心特性,允许开发者根据不同平台编写不同的代码,实现平台特有的功能。
8.1.1 模板条件编译
<template>
<view class="container">
<!-- #ifdef H5 -->
<text>H5平台特有内容</text>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN -->
<text>微信小程序特有内容</text>
<!-- #endif -->
<!-- #ifdef APP-PLUS -->
<text>App特有内容</text>
<!-- #endif -->
<!-- #ifdef MP-WEIXIN || MP-ALIPAY -->
<text>微信小程序和支付宝小程序共有内容</text>
<!-- #endif -->
</view>
</template>8.1.2 脚本条件编译
<script setup>
// #ifdef H5
console.log('H5平台');
// #endif
// #ifdef MP-WEIXIN
console.log('微信小程序');
// #endif
// #ifdef APP-PLUS
console.log('App平台');
// #endif
</script>8.1.3 样式条件编译
/* #ifdef H5 */
.container {
background-color: #f0f0f0;
}
/* #endif */
/* #ifdef MP-WEIXIN */
.container {
background-color: #ffffff;
}
/* #endif */8.2 平台特有API调用
// 调用微信小程序特有API
// #ifdef MP-WEIXIN
wx.requestSubscribeMessage({
tmplIds: ['template-id'],
success: (res) => {
console.log('Subscribe message success:', res);
}
});
// #endif
// 调用App特有API
// #ifdef APP-PLUS
plus.push.createMessage({
title: '推送标题',
content: '推送内容'
});
// #endif8.3 适配不同屏幕尺寸
8.3.1 使用rpx单位
<template>
<view class="container" style="width: 750rpx; height: 200rpx;">
<text style="font-size: 32rpx;">文本内容</text>
</view>
</template>8.3.2 动态适配
// 获取系统信息
uni.getSystemInfo({
success: (res) => {
// 计算屏幕宽度比例
const ratio = res.screenWidth / 750;
// 动态设置元素尺寸
const elementWidth = 200 * ratio;
console.log('Element width:', elementWidth);
}
});九、Uni-app打包与发布
9.1 打包H5
- 在HBuilderX中,点击"发行" -> "网站-H5手机版"
- 填写网站标题和域名
- 选择打包路径
- 点击"发行"按钮
- 打包完成后,在指定路径生成H5代码
9.2 打包微信小程序
- 在HBuilderX中,点击"发行" -> "小程序-微信"
- 填写小程序AppID
- 选择打包路径
- 点击"发行"按钮
- 打包完成后,在微信开发者工具中导入打包后的代码
- 在微信开发者工具中上传代码
- 在微信公众平台提交审核和发布
9.3 打包App
- 在HBuilderX中,点击"发行" -> "原生App-云打包"
- 选择打包平台(Android/iOS)
- 填写应用信息(包名、签名等)
- 选择打包类型(正式包/测试包)
- 点击"打包"按钮
- 等待打包完成,下载安装包
9.4 持续集成与自动化部署
# .github/workflows/uni-app.yml
name: Uni-app CI/CD
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '14.x'
- name: Install dependencies
run: npm install
- name: Build H5
run: npm run build:h5
- name: Deploy H5 to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist/build/h5
- name: Build WeChat Mini Program
run: npm run build:mp-weixin
- name: Upload WeChat Mini Program
# 使用微信小程序上传工具
run: |
npm install -g miniprogram-ci
miniprogram-ci upload --pp ./dist/build/mp-weixin --pkp ${{ secrets.WECHAT_PRIVATE_KEY_PATH }} --appid ${{ secrets.WECHAT_APPID }} --uv ${{ github.sha }} --desc "Auto deploy from GitHub Actions"十、Uni-app性能优化
10.1 代码优化
- 减少不必要的渲染:使用v-if和v-show合理控制元素渲染
- 使用虚拟列表:对于长列表,使用虚拟列表减少DOM节点数量
- 优化图片加载:使用图片懒加载,压缩图片大小
- 减少网络请求:合并请求,使用缓存
- 优化JavaScript代码:减少不必要的计算,使用防抖和节流
10.2 启动性能优化
- 减少启动页资源:简化启动页设计,减少资源加载
- 预加载关键资源:使用预加载API加载关键资源
- 延迟加载非关键资源:非关键资源延迟加载
- 优化代码包大小:移除不必要的依赖,使用按需引入
10.3 运行时性能优化
- 使用nvue页面:在App端使用nvue页面可以获得更好的性能
- 优化页面切换:减少页面切换时的资源加载
- 使用原生组件:对于性能要求高的场景,使用原生组件
- 优化动画效果:使用CSS动画代替JavaScript动画
- 及时清理资源:页面卸载时清理定时器、事件监听等
十一、Uni-app最佳实践
11.1 项目结构最佳实践
- 模块化开发:按功能模块组织代码,提高代码复用性
- 组件化设计:将通用UI组件抽象出来,便于维护和复用
- 状态管理:使用Pinia或Vuex进行状态管理,便于组件间通信
- API封装:封装网络请求和本地存储等API,便于统一管理
- 工具函数:将通用工具函数抽象出来,提高代码复用性
11.2 开发流程最佳实践
- 使用Git进行版本控制:便于团队协作和代码管理
- 编写单元测试:提高代码质量,减少bug
- 使用ESLint进行代码检查:保持代码风格一致
- 使用Prettier进行代码格式化:保持代码格式一致
- 定期进行代码审查:提高代码质量,分享知识
11.3 性能优化最佳实践
- 使用rpx单位:适配不同屏幕尺寸
- 合理使用条件编译:针对不同平台进行优化
- 优化图片资源:压缩图片大小,使用合适的格式
- 减少DOM节点数量:使用虚拟列表,合理嵌套组件
- 优化网络请求:合并请求,使用缓存,减少请求次数
十二、总结与展望
12.1 核心技术总结
- Uni-app框架:一套代码多端运行,支持Vue 3语法
- 项目结构:清晰的目录结构,便于维护和扩展
- 页面开发:丰富的生命周期和路由管理
- 组件开发:支持自定义组件和全局组件注册
- API调用:丰富的Uni-app API,支持调用各平台原生能力
- 状态管理:支持Pinia和Vuex进行状态管理
- 跨平台适配:条件编译和平台特有API调用
- 打包发布:支持多平台打包和发布
- 性能优化:多种性能优化策略
12.2 未来发展趋势
- 更好的Vue 3支持:Uni-app将继续优化对Vue 3的支持
- 更多平台支持:支持更多的小程序平台和新的跨平台技术
- 更好的性能:持续优化各平台的性能
- 更丰富的组件库:提供更多高质量的UI组件
- 更好的开发体验:优化HBuilderX和开发工具链
12.3 学习建议
- 官方文档:深入学习Uni-app官方文档,掌握核心概念和API
- 实战项目:通过实战项目掌握Uni-app开发技巧
- 社区资源:关注Uni-app社区,学习他人的经验和技巧
- 平台特性:了解各平台的特性和限制,便于进行跨平台适配
- 性能优化:学习性能优化技巧,提高应用的用户体验
通过本集的学习,相信你已经掌握了Uni-app框架的核心概念和深度使用技巧,能够使用Uni-app开发高质量的跨平台应用。在实际开发中,应根据项目需求和目标平台,选择合适的技术方案和优化策略,不断提升应用的质量和用户体验。