小程序开发规范 (Mini-App Development)
定位:uni-app x 小程序开发标准,一套代码多端发布,确保类型安全、分包优化、可维护性。
技术选型
| 层级 |
技术栈 |
版本要求 |
选型理由 |
| 框架 |
uni-app x |
≥ 4.0 |
基于 Vue 3、原生渲染、一套代码多端发布 |
| 开发语言 |
uts (Uni Type Script) |
- |
类型安全、编译到原生、性能接近原生 |
| UI 组件 |
uni-ui / uView |
最新 |
官方/社区组件库,跨端兼容 |
| 状态管理 |
Pinia |
≥ 2.0 |
Vue 3 官方推荐、TS 支持好 |
| 网络请求 |
uni.request 封装 |
- |
统一请求/响应处理 |
| 开发工具 |
HBuilderX |
最新 |
官方 IDE、调试方便 |
发布平台
| 平台 |
说明 |
| 微信小程序 |
主力平台 |
| 支付宝小程序 |
次要平台 |
| 抖音小程序 |
可选 |
| 快手小程序 |
可选 |
| H5 |
Web 应用 |
| App |
原生应用 |
核心优势:uni-app x 使用 uts 语言,类型安全 + 编译优化,一套代码多端运行。
项目结构
MyApp/
├── pages/ # 主包页面
│ ├── index/ # 首页
│ │ └── index.uvue
│ └── user/ # 用户
│ └── profile.uvue
├── pagesA/ # 分包 A
│ └── list/
│ └── list.uvue
├── pagesB/ # 分包 B
│ └── setting/
│ └── setting.uvue
├── components/ # 公共组件
│ ├── base/ # 基础组件
│ │ └── card.uvue
│ └── business/ # 业务组件
│ └── user-card.uvue
├── composables/ # 组合式函数
│ └── useAuth.uts
├── stores/ # Pinia 状态管理
│ └── user.uts
├── api/ # API 接口
│ ├── request.uts # 请求封装
│ └── modules/ # 按模块拆分
│ ├── user.uts
│ └── order.uts
├── utils/ # 工具函数
│ ├── common.uts # 业务工具
│ └── util.uts # 通用工具
├── static/ # 静态资源
│ └── images/
├── uni_modules/ # uni 插件
├── App.uvue # 应用入口
├── main.uts # 入口文件
├── pages.json # 页面配置
├── manifest.json # 应用配置
└── uni.scss # 全局样式变量
分包策略
pages.json 配置
{
"pages": [
{
"path": "pages/index/index",
"style": { "navigationBarTitleText": "首页" }
},
{
"path": "pages/user/profile",
"style": { "navigationBarTitleText": "我的" }
}
],
"subPackages": [
{
"root": "pagesA",
"name": "moduleA",
"pages": [
{ "path": "list/list" },
{ "path": "detail/detail" }
]
},
{
"root": "pagesB",
"name": "moduleB",
"pages": [
{ "path": "setting/setting" }
]
}
],
"preloadRule": {
"pages/index/index": {
"network": "all",
"packages": ["moduleA"]
}
}
}
分包原则
| 规则 |
说明 |
| 主包精简 |
仅放 TabBar 页面和公共资源,体积 < 2MB |
| 按功能分包 |
相关页面放同一分包 |
| 独立分包 |
不依赖主包内容的页面可设为独立分包 |
| 预加载 |
使用 preloadRule 提升用户体验 |
分包体积限制
| 平台 |
限制 |
| 微信小程序 |
整包 ≤ 20MB,单分包 ≤ 2MB |
| 支付宝小程序 |
整包 ≤ 20MB,单分包 ≤ 2MB |
| 抖音小程序 |
整包 ≤ 16MB,单分包 ≤ 2MB |
命名规范
| 类型 |
规范 |
示例 |
| 页面目录 |
小写 + 连字符 |
user-profile/ |
| 组件文件 |
小写 + 连字符 |
user-card.uvue |
| 变量/函数 |
camelCase |
getUserInfo |
| 常量 |
UPPER_SNAKE |
API_BASE_URL |
| 类型/接口 |
PascalCase |
UserInfo |
| 组合式函数 |
use 前缀 |
useAuth |
状态管理 (Pinia)
定义 Store
// stores/user.uts
import { defineStore } from 'pinia'
export const useUserStore = defineStore('user', () => {
const token = ref<string>('')
const userInfo = ref<UserInfo | null>(null)
const isLoggedIn = computed(() => !!token.value)
function setToken(newToken: string) {
token.value = newToken
}
function logout() {
token.value = ''
userInfo.value = null
// 清除本地存储
uni.removeStorageSync('token')
}
// 初始化时从本地存储恢复
function init() {
const savedToken = uni.getStorageSync('token')
if (savedToken) {
token.value = savedToken
}
}
return { token, userInfo, isLoggedIn, setToken, logout, init }
})
使用 Store
<script setup lang="uts">
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
const { userInfo, isLoggedIn } = storeToRefs(userStore)
const { logout, init } = userStore
// 页面加载时初始化
onLoad(() => {
init()
})
</script>
网络请求封装
请求工具
// api/request.uts
interface RequestOptions {
url: string
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
data?: any
showLoading?: boolean
loadingText?: string
}
interface ApiResponse<T> {
code: number
message: string
data: T
}
const API_BASE_URL = 'https://api.example.com'
export async function request<T>(options: RequestOptions): Promise<ApiResponse<T>> {
const { url, method = 'GET', data, showLoading = true, loadingText = '加载中' } = options
if (showLoading) {
uni.showLoading({ title: loadingText, mask: true })
}
return new Promise((resolve, reject) => {
uni.request({
url: API_BASE_URL + url,
method,
data,
header: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${uni.getStorageSync('token')}`
},
success: (res) => {
const result = res.data as ApiResponse<T>
if (result.code === 0) {
resolve(result)
} else if (result.code === 401) {
// Token 过期,跳转登录
uni.navigateTo({ url: '/pages/login/login' })
reject(result)
} else {
uni.showToast({ title: result.message, icon: 'none' })
reject(result)
}
},
fail: (err) => {
uni.showToast({ title: '网络错误', icon: 'none' })
reject(err)
},
complete: () => {
if (showLoading) uni.hideLoading()
}
})
})
}
API 模块化
// api/modules/user.uts
import { request, type ApiResponse } from '../request'
export interface UserInfo {
id: number
name: string
avatar: string
}
export const userApi = {
getUser: (id: number): Promise<ApiResponse<UserInfo>> => request({
url: `/user/${id}`
}),
updateUser: (data: Partial<UserInfo>): Promise<ApiResponse<UserInfo>> => request({
url: '/user',
method: 'PUT',
data
})
}
页面生命周期
<script setup lang="uts">
// 页面加载
onLoad((options) => {
console.log('页面参数:', options)
})
// 页面显示
onShow(() => {
console.log('页面显示')
})
// 页面隐藏
onHide(() => {
console.log('页面隐藏')
})
// 页面卸载
onUnload(() => {
console.log('页面卸载')
})
// 下拉刷新
onPullDownRefresh(() => {
// 刷新数据
uni.stopPullDownRefresh()
})
// 上拉加载
onReachBottom(() => {
// 加载更多
})
</script>
样式规范
rpx 响应式单位
/* 使用 rpx 适配不同屏幕(设计稿基准 750) */
.container {
width: 750rpx; /* 满屏宽度 */
padding: 32rpx;
font-size: 28rpx; /* 正文大小 */
}
全局样式变量 (uni.scss)
/* uni.scss */
$uni-color-primary: #07c160; /* 微信绿 */
$uni-color-success: #4cd964;
$uni-color-warning: #f0ad4e;
$uni-color-error: #dd524d;
$uni-spacing-sm: 16rpx;
$uni-spacing-md: 32rpx;
$uni-spacing-lg: 48rpx;
/* 文字颜色 */
$uni-text-color: #333;
$uni-text-color-secondary: #666;
$uni-text-color-placeholder: #999;
条件编译
/* #ifdef MP-WEIXIN */
.container { background-color: #07c160; }
/* #endif */
/* #ifdef MP-ALIPAY */
.container { background-color: #1677ff; }
/* #endif */
性能优化
| 策略 |
说明 |
| setData 精简 |
只传需要更新的数据,避免传大对象 |
| 图片懒加载 |
使用 lazy-load 属性 |
| 分包预加载 |
配置 preloadRule |
| 长列表虚拟化 |
使用 list + cell 组件 |
| 避免频繁 setData |
合并多次更新为一次 |
| 组件按需加载 |
使用 easycom 自动导入 |
长列表优化
<template>
<list class="list" :enable-back-to-top="true">
<cell v-for="item in list" :key="item.id">
<item-card :item="item" />
</cell>
<!-- 加载更多 -->
<cell>
<view class="loading" v-if="loading">加载中...</view>
<view class="no-more" v-else-if="noMore">没有更多了</view>
</cell>
</list>
</template>
条件编译
平台判断
// #ifdef MP-WEIXIN
console.log('微信小程序')
// #endif
// #ifdef MP-ALIPAY
console.log('支付宝小程序')
// #endif
// #ifdef MP-TOUTIAO
console.log('抖音小程序')
// #endif
平台差异化功能
<template>
<view>
<!-- #ifdef MP-WEIXIN -->
<button open-type="getPhoneNumber" @getphonenumber="onGetPhone">
获取手机号
</button>
<!-- #endif -->
<!-- #ifdef MP-ALIPAY -->
<button open-type="getAuthorize" @getAuthorize="onGetAuth">
授权登录
</button>
<!-- #endif -->
</view>
</template>
开发环境要求
| 项目 |
要求 |
| HBuilderX |
最新正式版 |
| 微信开发者工具 |
最新稳定版 |
| Node.js |
≥ 18(CLI 模式需要) |
| 内存 |
最低 8GB,推荐 16GB |
常见陷阱
| 问题 |
原因 |
解法 |
| 分包体积超限 |
单分包 > 2MB |
拆分或使用独立分包 |
| 首屏白屏 |
主包过大 |
精简主包,资源放分包 |
| setData 卡顿 |
传大对象 |
只传需要的字段 |
| 样式不生效 |
使用了不支持的 CSS |
查阅 uni-app x 支持的样式 |
| 图片不显示 |
路径错误或未配置域名 |
检查路径、配置合法域名 |
| 编译报错 |
uts 语法问题 |
检查类型定义 |
调试技巧
| 场景 |
方法 |
| 微信小程序 |
微信开发者工具预览 |
| 支付宝小程序 |
支付宝开发者工具 |
| 抖音小程序 |
字节开发者工具 |
| 网络请求 |
开发者工具 Network 面板 |
| 性能分析 |
开发者工具 Audits |
沉淀协议
| 场景 |
行动 |
| 组件复用 ≥ 2 次 |
提取到 components/base/ |
| API 调用模式重复 ≥ 3 次 |
封装到 api/modules/ |
| 状态逻辑重复 ≥ 2 次 |
提取到 stores/ 或 composables/ |
| 平台差异处理固化 |
更新本文档条件编译章节 |