第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 开发环境配置

  1. 安装Node.js:确保Node.js版本>=12.0.0
  2. 安装HBuilderX:Uni-app官方推荐的IDE,提供完整的开发体验
  3. 安装微信开发者工具:用于微信小程序预览和调试
  4. 安装Android Studio(可选):用于Android应用调试
  5. 安装Xcode(可选,Mac):用于iOS应用调试

2.2 使用HBuilderX创建Uni-app项目

  1. 打开HBuilderX,点击"文件" -> "新建" -> "项目"
  2. 选择"Uni-app",填写项目名称和路径
  3. 选择模板(默认模板、空模板、Hello uni-app等)
  4. 选择Vue版本(Vue 3推荐)
  5. 点击"创建"按钮

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 pinia

7.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: '推送内容'
});
// #endif

8.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

  1. 在HBuilderX中,点击"发行" -> "网站-H5手机版"
  2. 填写网站标题和域名
  3. 选择打包路径
  4. 点击"发行"按钮
  5. 打包完成后,在指定路径生成H5代码

9.2 打包微信小程序

  1. 在HBuilderX中,点击"发行" -> "小程序-微信"
  2. 填写小程序AppID
  3. 选择打包路径
  4. 点击"发行"按钮
  5. 打包完成后,在微信开发者工具中导入打包后的代码
  6. 在微信开发者工具中上传代码
  7. 在微信公众平台提交审核和发布

9.3 打包App

  1. 在HBuilderX中,点击"发行" -> "原生App-云打包"
  2. 选择打包平台(Android/iOS)
  3. 填写应用信息(包名、签名等)
  4. 选择打包类型(正式包/测试包)
  5. 点击"打包"按钮
  6. 等待打包完成,下载安装包

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 代码优化

  1. 减少不必要的渲染:使用v-if和v-show合理控制元素渲染
  2. 使用虚拟列表:对于长列表,使用虚拟列表减少DOM节点数量
  3. 优化图片加载:使用图片懒加载,压缩图片大小
  4. 减少网络请求:合并请求,使用缓存
  5. 优化JavaScript代码:减少不必要的计算,使用防抖和节流

10.2 启动性能优化

  1. 减少启动页资源:简化启动页设计,减少资源加载
  2. 预加载关键资源:使用预加载API加载关键资源
  3. 延迟加载非关键资源:非关键资源延迟加载
  4. 优化代码包大小:移除不必要的依赖,使用按需引入

10.3 运行时性能优化

  1. 使用nvue页面:在App端使用nvue页面可以获得更好的性能
  2. 优化页面切换:减少页面切换时的资源加载
  3. 使用原生组件:对于性能要求高的场景,使用原生组件
  4. 优化动画效果:使用CSS动画代替JavaScript动画
  5. 及时清理资源:页面卸载时清理定时器、事件监听等

十一、Uni-app最佳实践

11.1 项目结构最佳实践

  1. 模块化开发:按功能模块组织代码,提高代码复用性
  2. 组件化设计:将通用UI组件抽象出来,便于维护和复用
  3. 状态管理:使用Pinia或Vuex进行状态管理,便于组件间通信
  4. API封装:封装网络请求和本地存储等API,便于统一管理
  5. 工具函数:将通用工具函数抽象出来,提高代码复用性

11.2 开发流程最佳实践

  1. 使用Git进行版本控制:便于团队协作和代码管理
  2. 编写单元测试:提高代码质量,减少bug
  3. 使用ESLint进行代码检查:保持代码风格一致
  4. 使用Prettier进行代码格式化:保持代码格式一致
  5. 定期进行代码审查:提高代码质量,分享知识

11.3 性能优化最佳实践

  1. 使用rpx单位:适配不同屏幕尺寸
  2. 合理使用条件编译:针对不同平台进行优化
  3. 优化图片资源:压缩图片大小,使用合适的格式
  4. 减少DOM节点数量:使用虚拟列表,合理嵌套组件
  5. 优化网络请求:合并请求,使用缓存,减少请求次数

十二、总结与展望

12.1 核心技术总结

  1. Uni-app框架:一套代码多端运行,支持Vue 3语法
  2. 项目结构:清晰的目录结构,便于维护和扩展
  3. 页面开发:丰富的生命周期和路由管理
  4. 组件开发:支持自定义组件和全局组件注册
  5. API调用:丰富的Uni-app API,支持调用各平台原生能力
  6. 状态管理:支持Pinia和Vuex进行状态管理
  7. 跨平台适配:条件编译和平台特有API调用
  8. 打包发布:支持多平台打包和发布
  9. 性能优化:多种性能优化策略

12.2 未来发展趋势

  1. 更好的Vue 3支持:Uni-app将继续优化对Vue 3的支持
  2. 更多平台支持:支持更多的小程序平台和新的跨平台技术
  3. 更好的性能:持续优化各平台的性能
  4. 更丰富的组件库:提供更多高质量的UI组件
  5. 更好的开发体验:优化HBuilderX和开发工具链

12.3 学习建议

  1. 官方文档:深入学习Uni-app官方文档,掌握核心概念和API
  2. 实战项目:通过实战项目掌握Uni-app开发技巧
  3. 社区资源:关注Uni-app社区,学习他人的经验和技巧
  4. 平台特性:了解各平台的特性和限制,便于进行跨平台适配
  5. 性能优化:学习性能优化技巧,提高应用的用户体验

通过本集的学习,相信你已经掌握了Uni-app框架的核心概念和深度使用技巧,能够使用Uni-app开发高质量的跨平台应用。在实际开发中,应根据项目需求和目标平台,选择合适的技术方案和优化策略,不断提升应用的质量和用户体验。

« 上一篇 Vue 3 大数据量处理方案深度指南:高效处理海量数据 下一篇 » Vue 3 Taro多端开发深度指南:跨框架跨平台解决方案