第244集:Vue 3 小程序开发实践深度指南

概述

小程序作为一种轻量级的应用形态,已经成为移动互联网生态的重要组成部分。本集将深入探讨基于Vue 3的小程序开发实践,涵盖从环境搭建到发布上线的完整流程。我们将重点介绍小程序的核心概念、项目结构、组件开发、API调用、状态管理以及性能优化等关键技术,帮助你掌握高质量小程序开发的核心技能。

一、小程序核心概念

1.1 小程序的定义与特点

  • 定义:小程序是一种不需要下载安装即可使用的应用,用户扫一扫或搜一下即可打开应用
  • 特点
    • 无需安装,触手可及
    • 用完即走,不占内存
    • 开发门槛低,成本低
    • 基于Web技术,同时具有原生应用的体验
    • 依托于各大平台生态,流量巨大

1.2 小程序技术架构

┌──────────────────────┐
│    渲染层           │
│  ┌────────────────┐ │
│  │  WXML模板      │ │
│  └────────────────┘ │
│  ┌────────────────┐ │
│  │  WXSS样式      │ │
│  └────────────────┘ │
└──────────────────────┘
        │
        ▼
┌──────────────────────┐
│    逻辑层           │
│  ┌────────────────┐ │
│  │  JavaScript    │ │
│  └────────────────┘ │
│  ┌────────────────┐ │
│  │  小程序API     │ │
│  └────────────────┘ │
└──────────────────────┘
        │
        ▼
┌──────────────────────┐
│    原生层           │
│  ┌────────────────┐ │
│  │  平台原生能力   │ │
│  └────────────────┘ │
└──────────────────────┘

1.3 小程序与Web应用的区别

特性 小程序 Web应用
运行环境 小程序运行时 浏览器
开发语言 WXML/WXSS/JS HTML/CSS/JS
API调用 平台提供的原生API Web API
性能 接近原生应用 取决于浏览器性能
发布方式 平台审核发布 自行部署
访问方式 平台内访问 浏览器URL访问
资源限制 单个包大小限制(一般2M) 无硬性限制

二、开发环境搭建

2.1 工具准备

  1. 微信开发者工具:用于微信小程序开发、预览和调试
  2. HBuilderX:支持多端小程序开发,提供良好的开发体验
  3. Node.js:确保Node.js版本>=14.0.0
  4. Vue 3:使用Vue 3开发小程序

2.2 使用HBuilderX创建小程序项目

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

2.3 项目目录结构

┌─uni_modules/          # 插件市场模块
├─pages/                # 页面目录
│  └─index/             # 首页
│     ├─index.vue       # 首页组件
│     └─index.json      # 页面配置
├─static/               # 静态资源目录
├─components/           # 自定义组件目录
├─common/               # 公共资源目录
├─store/                # 状态管理目录
├─App.vue               # 应用入口组件
├─main.js               # 应用入口文件
├─manifest.json         # 应用配置文件
├─pages.json            # 页面路由配置
├─uni.scss              # 全局样式
└─package.json          # 项目依赖配置

三、小程序页面开发

3.1 页面配置

// pages/index/index.json
{
  "navigationBarTitleText": "首页",
  "navigationBarBackgroundColor": "#ffffff",
  "navigationBarTextStyle": "black",
  "enablePullDownRefresh": true,
  "onReachBottomDistance": 50
}

3.2 页面生命周期

<template>
  <view class="page-container">
    <text>页面内容</text>
  </view>
</template>

<script setup>
import { onMounted, onUnmounted } from 'vue';
import { onLoad, onShow, onHide, onUnload, onPullDownRefresh, onReachBottom } from '@dcloudio/uni-app';

// Vue 3 生命周期
onMounted(() => {
  console.log('Vue onMounted');
});

onUnmounted(() => {
  console.log('Vue onUnmounted');
});

// 小程序页面生命周期
onLoad((options) => {
  console.log('页面加载', options);
  // 页面初始化,获取URL参数
});

onShow(() => {
  console.log('页面显示');
  // 页面显示时执行
});

onHide(() => {
  console.log('页面隐藏');
  // 页面隐藏时执行
});

onUnload(() => {
  console.log('页面卸载');
  // 页面卸载时执行,清理资源
});

onPullDownRefresh(() => {
  console.log('下拉刷新');
  // 下拉刷新时执行
  // 停止下拉刷新
  uni.stopPullDownRefresh();
});

onReachBottom(() => {
  console.log('上拉触底');
  // 上拉触底时执行,用于分页加载
});
</script>

3.3 页面路由与跳转

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

<!-- 关闭所有页面,打开到应用内的某个页面 -->
<navigator url="/pages/index/index" open-type="reLaunch">
  返回首页
</navigator>

3.3.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 // 返回的页面数
});

3.4 页面参数传递与接收

3.4.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' });
  }
});

3.4.2 接收参数

// 方式1:onLoad接收URL参数
onLoad((options) => {
  console.log('Received params:', options);
  // options.id, options.name
});

// 方式2:事件通道接收数据
onLoad(() => {
  const eventChannel = uni.getOpenerEventChannel();
  if (eventChannel) {
    eventChannel.on('acceptDataFromOpenerPage', (data) => {
      console.log('Received data:', data);
    });
  }
});

四、小程序组件开发

4.1 自定义组件基础

4.1.1 创建自定义组件

<!-- components/CustomButton/CustomButton.vue -->
<template>
  <button 
    class="custom-button"
    :class="{ 'custom-button--primary': type === 'primary' }"
    @click="handleClick"
  >
    <slot></slot>
  </button>
</template>

<script setup>
// 组件属性
interface Props {
  type?: 'default' | 'primary' | 'success' | 'warning' | 'danger';
  size?: 'small' | 'medium' | 'large';
  disabled?: boolean;
}

const props = withDefaults(defineProps<Props>(), {
  type: 'default',
  size: 'medium',
  disabled: false
});

// 组件事件
const emit = defineEmits(['click']);

const handleClick = () => {
  if (!props.disabled) {
    emit('click');
  }
};
</script>

<style scoped lang="scss">
.custom-button {
  padding: 10px 20px;
  border: 1px solid #ddd;
  border-radius: 4px;
  background-color: #fff;
  font-size: 14px;
  cursor: pointer;
  
  &--primary {
    background-color: #007aff;
    color: #fff;
    border-color: #007aff;
  }
  
  &--success {
    background-color: #4cd964;
    color: #fff;
    border-color: #4cd964;
  }
  
  &--warning {
    background-color: #ff9500;
    color: #fff;
    border-color: #ff9500;
  }
  
  &--danger {
    background-color: #ff3b30;
    color: #fff;
    border-color: #ff3b30;
  }
  
  &--small {
    padding: 5px 10px;
    font-size: 12px;
  }
  
  &--large {
    padding: 15px 30px;
    font-size: 16px;
  }
  
  &:disabled {
    opacity: 0.5;
    cursor: not-allowed;
  }
}
</style>

4.1.2 使用自定义组件

<template>
  <view class="container">
    <CustomButton @click="handleButtonClick">
      默认按钮
    </CustomButton>
    
    <CustomButton type="primary" @click="handleButtonClick">
      主要按钮
    </CustomButton>
    
    <CustomButton type="success" size="large" @click="handleButtonClick">
      成功按钮
    </CustomButton>
    
    <CustomButton type="danger" disabled>
      禁用按钮
    </CustomButton>
  </view>
</template>

<script setup>
// 引入自定义组件
import CustomButton from '@/components/CustomButton/CustomButton.vue';

const handleButtonClick = () => {
  console.log('Button clicked');
};
</script>

4.2 组件通信

4.2.1 Props与事件

<!-- 父组件 -->
<template>
  <view class="container">
    <ChildComponent 
      :message="parentMessage"
      @update:message="parentMessage = $event"
    ></ChildComponent>
  </view>
</template>

<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';

const parentMessage = ref('Hello from parent');
</script>

<!-- 子组件 -->
<template>
  <view class="child-container">
    <text>{{ message }}</text>
    <button @click="updateMessage">更新消息</button>
  </view>
</template>

<script setup>
interface Props {
  message: string;
}

const props = defineProps<Props>();
const emit = defineEmits(['update:message']);

const updateMessage = () => {
  emit('update:message', 'Hello from child');
};
</script>

4.2.2 依赖注入(Provide/Inject)

<!-- 父组件 -->
<template>
  <view class="container">
    <ChildComponent></ChildComponent>
  </view>
</template>

<script setup>
import { provide } from 'vue';
import ChildComponent from './ChildComponent.vue';

// 提供数据
provide('userInfo', {
  name: '张三',
  age: 25
});
</script>

<!-- 子组件 -->
<template>
  <view class="child-container">
    <text>{{ userInfo.name }}</text>
  </view>
</template>

<script setup>
import { inject } from 'vue';

// 注入数据
interface UserInfo {
  name: string;
  age: number;
}

const userInfo = inject<UserInfo>('userInfo', { name: '', age: 0 });
</script>

五、小程序API调用

5.1 网络请求

// 发起GET请求
uni.request({
  url: 'https://api.example.com/users',
  method: 'GET',
  data: {
    page: 1,
    pageSize: 10
  },
  header: {
    'Content-Type': 'application/json'
  },
  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);
    }
  }
});

5.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');
  }
});

// 同步方法
uni.setStorageSync('userInfo', { name: '张三', age: 25 });
const userInfo = uni.getStorageSync('userInfo');
uni.removeStorageSync('userInfo');
uni.clearStorageSync();

5.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);
  }
});

// 导航栏加载动画
uni.showNavigationBarLoading();
uni.hideNavigationBarLoading();

// 设置导航栏标题
uni.setNavigationBarTitle({
  title: '新标题'
});

5.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.scanCode({
  success: (res) => {
    console.log('Scan result:', res.result);
  }
});

// 选择图片
uni.chooseImage({
  count: 3,
  success: (res) => {
    console.log('Selected images:', res.tempFilePaths);
  }
});

六、小程序状态管理

6.1 使用Pinia进行状态管理

6.1.1 安装Pinia

npm install pinia @dcloudio/uni-pinia

6.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
  };
}

6.1.3 创建Store

// store/user.js
import { defineStore } from 'pinia';

interface UserInfo {
  name: string;
  age: number;
  avatar?: string;
}

export const useUserStore = defineStore('user', {
  state: () => ({
    userInfo: null as UserInfo | null,
    token: '',
    isLoggedIn: false
  }),
  
  getters: {
    userName: (state) => state.userInfo?.name || '',
    userAvatar: (state) => state.userInfo?.avatar || ''
  },
  
  actions: {
    login(userInfo: UserInfo, token: string) {
      this.userInfo = userInfo;
      this.token = token;
      this.isLoggedIn = true;
      // 保存到本地存储
      uni.setStorageSync('userInfo', userInfo);
      uni.setStorageSync('token', token);
    },
    
    logout() {
      this.userInfo = null;
      this.token = '';
      this.isLoggedIn = false;
      // 清除本地存储
      uni.removeStorageSync('userInfo');
      uni.removeStorageSync('token');
    },
    
    init() {
      // 从本地存储恢复状态
      const userInfo = uni.getStorageSync('userInfo');
      const token = uni.getStorageSync('token');
      if (userInfo && token) {
        this.userInfo = userInfo;
        this.token = token;
        this.isLoggedIn = true;
      }
    }
  }
});

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

七、小程序性能优化

7.1 代码优化

  1. 减少不必要的渲染:使用v-if和v-show合理控制元素渲染
  2. 使用虚拟列表:对于长列表使用虚拟滚动技术
  3. 优化图片加载:使用图片懒加载,压缩图片大小
  4. 减少JavaScript执行时间:优化JavaScript代码,避免复杂计算
  5. 使用缓存:缓存频繁使用的数据
  6. 减少网络请求:合并请求,使用缓存

7.2 启动性能优化

  1. 减少包大小:移除不必要的依赖,压缩资源
  2. 分包加载:使用分包加载减少主包大小
  3. 预加载资源:预加载可能需要的资源
  4. 优化首屏渲染:减少首屏渲染的DOM节点数量
  5. 使用骨架屏:提供良好的加载体验

7.3 运行时性能优化

  1. 优化setData调用:减少setData调用次数,合并数据
  2. 避免频繁操作DOM:减少DOM操作,使用虚拟DOM
  3. 使用原生组件:对于性能要求高的场景使用原生组件
  4. 优化动画效果:使用CSS动画代替JavaScript动画
  5. 及时清理资源:页面卸载时清理定时器、事件监听等

7.4 网络优化

  1. 使用HTTPS:提高网络请求的安全性和速度
  2. 使用CDN:加速资源加载
  3. 减少请求次数:合并请求,使用缓存
  4. 使用WebSocket:对于实时数据使用WebSocket
  5. 优化请求时机:合理安排请求时机,避免不必要的请求

八、小程序发布与上线

8.1 代码审核

  1. 准备审核材料:应用介绍、截图、测试账号等
  2. 遵循平台规范:遵守各平台的审核规范
  3. 测试充分:确保应用功能正常,无崩溃问题
  4. 优化用户体验:提供良好的用户体验
  5. 关注审核反馈:及时处理审核不通过的问题

8.2 发布流程

  1. 打包上传:在开发者工具中打包上传代码
  2. 提交审核:在平台后台提交审核
  3. 审核通过:审核通过后发布上线
  4. 灰度发布:部分平台支持灰度发布
  5. 全量发布:灰度发布无问题后全量发布

8.3 运营与维护

  1. 数据分析:分析用户行为数据,优化产品
  2. 版本更新:定期更新版本,修复问题,添加新功能
  3. 用户反馈:及时处理用户反馈
  4. 性能监控:监控应用性能,及时优化
  5. 安全防护:加强应用安全防护

九、小程序开发最佳实践

9.1 项目结构最佳实践

  1. 模块化开发:按功能模块组织代码
  2. 组件化设计:将通用UI组件抽象出来
  3. 清晰的目录结构:合理组织代码目录
  4. 统一的命名规范:使用一致的命名规范
  5. 文档化:编写必要的文档

9.2 开发流程最佳实践

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

9.3 用户体验最佳实践

  1. 简洁的界面设计:避免复杂的界面设计
  2. 流畅的交互体验:提供流畅的交互体验
  3. 清晰的导航:提供清晰的导航结构
  4. 良好的加载体验:使用骨架屏、加载动画等
  5. 适配不同设备:适配不同屏幕尺寸的设备

9.4 性能优化最佳实践

  1. 使用rpx单位:适配不同屏幕尺寸
  2. 优化图片资源:压缩图片大小,使用合适的格式
  3. 减少DOM节点数量:优化DOM结构
  4. 使用缓存:合理使用缓存
  5. 优化网络请求:减少请求次数,使用CDN

十、总结与展望

10.1 核心技术总结

  1. 小程序基础:了解小程序的核心概念和技术架构
  2. 页面开发:掌握小程序页面的开发流程和生命周期
  3. 组件开发:掌握自定义组件的开发和通信机制
  4. API调用:熟悉小程序API的使用
  5. 状态管理:使用Pinia进行状态管理
  6. 性能优化:掌握小程序性能优化的各种策略
  7. 发布上线:了解小程序的发布流程和运营维护

10.2 未来发展趋势

  1. 跨平台开发:使用Uni-app、Taro等框架实现一套代码多端运行
  2. 云开发:使用云开发降低后端开发成本
  3. AI集成:集成AI功能,提供更智能的服务
  4. AR/VR:集成AR/VR技术,提供更丰富的体验
  5. WebAssembly:使用WebAssembly提高性能
  6. 开放平台:小程序开放平台,提供更多的生态服务

10.3 学习建议

  1. 官方文档:深入学习各平台的官方文档
  2. 实战项目:通过实战项目掌握开发技巧
  3. 关注新技术:关注小程序开发的新技术和趋势
  4. 社区资源:参与社区讨论,学习他人的经验
  5. 持续学习:小程序技术发展迅速,需要持续学习

通过本集的学习,相信你已经掌握了Vue 3小程序开发的核心技术和最佳实践,能够开发出高质量的小程序应用。在实际开发中,应根据项目需求和平台规范,选择合适的技术方案,并持续优化性能和用户体验。

« 上一篇 Vue 3 Flutter与Vue集成深度指南:跨框架协作 下一篇 » Vue 3 跨平台组件库开发:构建多端统一组件系统