UniApp 开发小程序知识总结与面试题
UniApp 跨端开发的系统总结,涵盖目录与文件结构、页面与组件生命周期、pages.json 配置、条件编译、多端适配与性能优化,一套代码编译到小程序、H5 与 App。
一、UniApp 基础概念
1. 什么是 UniApp?
- 一个使用 Vue.js 开发跨平台应用的前端框架
- 一套代码,可发布到 iOS、Android、H5、微信小程序、支付宝小程序等多个平台
- 基于 Vue 2/3,兼容 npm 生态
2. UniApp 架构特点
- 跨平台能力:一次开发,多端运行
- 原生体验:通过原生渲染引擎实现接近原生的性能
- Vue 语法:完全支持 Vue 2/3 的语法和生命周期
- 小程序生态:支持微信、支付宝、百度等多个小程序平台
3. 与原生小程序的区别
| 维度 | UniApp | 原生小程序 |
|---|---|---|
| 技术栈 | Vue + TypeScript | WXML + WXSS + JS |
| 跨平台 | 支持多端 | 仅微信小程序 |
| 开发体验 | 类 Vue 开发 | 小程序特有语法 |
| 生态 | npm 生态 | 小程序生态 |
二、核心文件结构
├── src/│ ├── App.vue # 根组件│ ├── main.ts # 入口文件│ ├── pages.json # 页面路由配置│ ├── manifest.json # 应用配置│ ├── pages/ # 页面目录│ │ ├── index/│ │ │ └── index.vue│ └── components/ # 组件目录├── package.json└── vite.config.ts # Vite 配置三、生命周期详解
1. 应用生命周期
export default { onLaunch() { // 应用启动时触发 }, onShow() { // 应用显示时触发 }, onHide() { // 应用隐藏时触发 }}2. 页面生命周期
export default { onLoad(options) { // 页面加载时触发 }, onShow() { // 页面显示时触发 }, onReady() { // 页面初次渲染完成 }, onHide() { // 页面隐藏时触发 }, onUnload() { // 页面卸载时触发 }, onPullDownRefresh() { // 下拉刷新触发 }, onReachBottom() { // 上拉触底触发 }}3. 组件生命周期(Vue 标准)
beforeCreate→created→beforeMount→mountedbeforeUpdate→updatedbeforeUnmount→unmounted
四、常用 API
1. 网络请求
// uni.request 封装const request = (options: UniApp.RequestOptions) => { return new Promise((resolve, reject) => { uni.request({ ...options, success: (res) => resolve(res), fail: (err) => reject(err) }) })}// 使用示例const data = await request({ url: 'https://api.example.com/data', method: 'GET'})2. 页面路由
| API | 说明 |
|---|---|
uni.navigateTo |
保留当前页面,跳转到新页面 |
uni.redirectTo |
关闭当前页面,跳转到新页面 |
uni.switchTab |
跳转到 tabBar 页面 |
uni.navigateBack |
返回上一页 |
uni.reLaunch |
关闭所有页面,打开新页面 |
3. 数据存储
// 存储uni.setStorageSync('user', { name: 'test' })// 读取const user = uni.getStorageSync('user')// 删除uni.removeStorageSync('user')4. 界面交互
// 显示 Toastuni.showToast({ title: '成功', icon: 'success'})// 显示加载框uni.showLoading({ title: '加载中...'})// 显示模态框uni.showModal({ title: '提示', content: '确定删除?', success: (res) => { if (res.confirm) { // 用户点击确定 } }})五、组件化开发
1. 创建组件
<!-- components/MyComponent.vue --><template> <view class="my-component"> <text>{{ title }}</text> <button @click="handleClick">点击</button> </view></template><script setup lang="ts">import { defineProps, defineEmits } from 'vue'const props = defineProps<{ title: string}>()const emit = defineEmits<{ (e: 'click', value: string): void}>()const handleClick = () => { emit('click', 'hello')}</script>2. 使用组件
<template> <MyComponent title="标题" @click="onClick" /></template><script setup lang="ts">import MyComponent from '@/components/MyComponent.vue'const onClick = (value: string) => { console.log(value)}</script>六、pages.json 配置详解
1. 基本配置
{ "pages": [ { "path": "pages/index/index", "style": { "navigationBarTitleText": "首页", "navigationStyle": "custom" } } ], "globalStyle": { "navigationBarTextStyle": "black", "navigationBarTitleText": "UniApp", "navigationBarBackgroundColor": "#ffffff", "backgroundColor": "#f5f5f5" }, "tabBar": { "color": "#999999", "selectedColor": "#007aff", "list": [ { "pagePath": "pages/index/index", "text": "首页", "iconPath": "static/tabs/home.png", "selectedIconPath": "static/tabs/home-active.png" } ] }}七、条件编译
1. 平台标识
| 标识 | 平台 |
|---|---|
H5 |
H5 |
MP-WEIXIN |
微信小程序 |
MP-ALIPAY |
支付宝小程序 |
APP-PLUS |
App |
2. 使用示例
// #ifdef H5console.log('H5 平台')// #endif// #ifndef MP-WEIXINconsole.log('非微信小程序')// #endif<!-- #ifdef H5 --><view>H5 专属内容</view><!-- #endif -->八、性能优化
1. 分包加载
{ "pages": [ { "path": "pages/index/index" } ], "subpackages": [ { "root": "pages/subpackage", "pages": ["page1", "page2"] } ]}2. 图片优化
- 使用合适尺寸的图片
- 开启懒加载:
image组件添加lazy-load属性 - 使用 WebP 格式
3. 代码优化
- 避免频繁调用
uni.setData - 使用 computed 和 watch 优化数据响应
- 合理使用组件缓存
4. 首屏优化
- 减少首屏资源请求
- 使用骨架屏
- 异步加载非首屏数据
九、常见面试题
基础概念
Q1:什么是 UniApp?它的优势是什么?
- 基于 Vue.js 的跨平台框架
- 一套代码多端运行(小程序、H5、App)
- 原生渲染,性能接近原生
- 支持 npm 生态,开发体验好
Q2:UniApp 和原生小程序的区别?
- 技术栈:UniApp 使用 Vue,原生使用 WXML/WXSS
- 跨平台:UniApp 支持多端,原生仅支持微信
- 开发效率:UniApp 更高,复用性强
Q3:UniApp 的生命周期有哪些?
- 应用生命周期:onLaunch、onShow、onHide
- 页面生命周期:onLoad、onShow、onReady、onHide、onUnload
- 组件生命周期:Vue 标准生命周期
进阶问题
Q4:UniApp 如何实现跨平台兼容?
- 通过条件编译区分不同平台
- 使用统一的 API 层封装不同平台的实现
- 编译时根据目标平台生成对应代码
Q5:如何在 UniApp 中使用 TypeScript?
- 创建项目时选择 TypeScript 模板
- 在
tsconfig.json中配置路径别名 - 使用
.vue文件时添加lang="ts"
Q6:UniApp 的路由跳转方式有哪些?
navigateTo:保留当前页面redirectTo:关闭当前页面switchTab:跳转到 tabBar 页面reLaunch:关闭所有页面
性能优化
Q7:如何优化 UniApp 小程序的性能?
- 分包加载,减小主包体积
- 优化图片,使用懒加载
- 减少 setData 调用次数和数据量
- 使用骨架屏提升首屏体验
Q8:UniApp 小程序的包体积限制是多少?如何优化?
- 主包:2MB,分包:20MB
- 优化:代码压缩、图片优化、分包加载、删除无用代码
实战经验
Q9:如何处理 UniApp 中的跨域问题?
- 开发环境:配置
h5端的devServer.proxy - 小程序:在后台配置合法域名白名单
Q10:UniApp 如何调用原生能力?
- 使用官方提供的 API(如
uni.chooseImage) - 通过
uni.requireNativePlugin调用原生插件 - 使用 App 端的 Native.js
原理与架构
Q11:UniApp 的编译原理是什么?如何将 Vue 代码转换为小程序代码?
编译流程:
- 代码解析:解析
.vue文件,提取 template、script、style - AST 转换:将 Vue 的 AST 转换为小程序的 AST
- 代码生成:根据目标平台生成对应的代码
- 微信小程序:生成
.wxml、.wxss、.js、.json - H5:生成标准 HTML/CSS/JS
- 微信小程序:生成
- 资源处理:处理图片、字体等静态资源
核心转换逻辑:
v-if→wx:ifv-for→wx:for@click→bindtap:class→class(动态绑定)
编译器核心代码:
// 简化的标签转换逻辑const tagMap = { 'view': 'view', 'text': 'text', 'image': 'image', 'button': 'button'}function transformTag(tag) { return tagMap[tag] || 'view'}// 属性转换const attrMap = { ':class': 'class', ':style': 'style', '@click': 'bindtap', '@input': 'bindinput'}Q12:UniApp 的渲染原理是什么?为什么能实现接近原生的性能?
渲染架构:
- 逻辑层:运行在 JavaScript 引擎中,处理业务逻辑
- 视图层:运行在原生渲染引擎中,负责 UI 渲染
- 通信机制:逻辑层和视图层通过桥接层通信
性能优化手段:
- 原生组件渲染:使用原生组件而非 WebView 渲染
- 双线程分离:逻辑和渲染分离,避免阻塞
- 批量更新:合并多次
setData调用,减少通信开销 - 缓存策略:组件缓存、图片缓存、页面缓存
setData 优化示例:
// 不好的写法:多次调用this.setData({ list: newList1 })this.setData({ count: newCount })// 好的写法:合并调用this.setData({ list: newList1, count: newCount})高级组件开发
Q13:如何在 UniApp 中封装一个高性能的列表组件?需要注意哪些问题?
实现要点:
<template> <scroll-view scroll-y @scrolltolower="loadMore" :scroll-top="scrollTop" > <view v-for="(item, index) in list" :key="item.id"> <slot :item="item" :index="index" /> </view> <view v-if="loading" class="loading">加载中...</view> <view v-if="noMore" class="no-more">没有更多了</view> </scroll-view></template><script setup lang="ts">import { ref, computed } from 'vue'const props = defineProps<{ list: any[] loading: boolean noMore: boolean}>()const emit = defineEmits<{ (e: 'loadMore'): void}>()const scrollTop = ref(0)const loadMore = () => { if (!props.loading && !props.noMore) { emit('loadMore') }}</script>注意事项:
- key 的使用:必须使用唯一的 key,避免列表错乱
- 避免复杂计算:列表项中避免使用复杂的 computed 属性
- 图片懒加载:使用
lazy-load属性 - 虚拟化:长列表(>1000条)考虑使用虚拟列表
Q14:如何实现 UniApp 组件的自定义事件和双向绑定?
自定义事件:
<!-- 子组件 --><script setup lang="ts">const emit = defineEmits<{ (e: 'update:modelValue', value: string): void (e: 'change', value: string): void}>()const handleInput = (e: Event) => { const value = (e.target as HTMLInputElement).value emit('update:modelValue', value) emit('change', value)}</script>双向绑定:
<!-- 父组件 --><template> <CustomInput v-model="inputValue" @change="onChange" /></template><script setup lang="ts">import { ref } from 'vue'import CustomInput from '@/components/CustomInput.vue'const inputValue = ref('')const onChange = (value: string) => { console.log('值改变:', value)}</script>跨平台兼容
Q15:UniApp 中不同平台的 API 差异如何处理?有哪些兼容性方案?
方案一:条件编译
// #ifdef MP-WEIXINuni.setNavigationBarColor({ frontColor: '#ffffff', backgroundColor: '#007aff'})// #endif// #ifdef H5document.querySelector('title')!.textContent = '新标题'// #endif方案二:统一封装
export const setTitle = (title: string) => { // #ifdef MP-WEIXIN uni.setNavigationBarTitle({ title }) // #endif // #ifdef H5 document.title = title // #endif}方案三:API 能力检测
const shareData = { title: '分享标题', path: '/pages/index/index'}if (uni.share) { uni.share(shareData)} else { uni.showToast({ title: '当前平台不支持分享', icon: 'none' })}常见平台差异:
| 平台 | 差异点 |
|---|---|
| 微信小程序 | 支付、分享、登录 API 特有 |
| 支付宝小程序 | 芝麻信用、生活号 API 特有 |
| H5 | 支持 DOM API,有跨域问题 |
| App | 支持 Native.js、原生插件 |
状态管理
Q16:UniApp 中如何设计状态管理?Pinia 和 Vuex 在小程序中的使用有什么注意事项?
Pinia 在 UniApp 中的配置:
// stores/user.tsimport { defineStore } from 'pinia'import { ref, computed } from 'vue'export const useUserStore = defineStore('user', () => { const userInfo = ref(null) const token = ref('') const isLoggedIn = computed(() => !!token.value) const login = (data: { user: any; token: string }) => { userInfo.value = data.user token.value = data.token uni.setStorageSync('token', data.token) } const logout = () => { userInfo.value = null token.value = '' uni.removeStorageSync('token') } return { userInfo, token, isLoggedIn, login, logout }})使用注意事项:
- 持久化:小程序端需手动处理 localStorage,H5 可使用
pinia-plugin-persistedstate - SSR 兼容性:小程序端无服务端渲染,无需考虑
- 性能:避免在 store 中存储过大的数据
- 模块化:按业务模块拆分 store
在组件中使用:
<script setup lang="ts">import { useUserStore } from '@/stores/user'const userStore = useUserStore()const handleLogin = () => { userStore.login({ user: { name: 'test' }, token: 'xxx' })}</script>微信云开发
Q17:如何在 UniApp 中集成微信云开发?云函数和云数据库的使用场景是什么?
初始化云开发:
// App.vueonLaunch() { // #ifdef MP-WEIXIN wx.cloud.init({ env: 'your-env-id', traceUser: true }) // #endif}云数据库操作:
// 获取数据库实例const db = wx.cloud.database()// 查询数据const queryData = async () => { const res = await db.collection('users') .where({ status: 1 }) .orderBy('createTime', 'desc') .limit(10) .get() return res.data}// 添加数据const addData = async (data: any) => { const res = await db.collection('users').add({ data }) return res._id}// 更新数据const updateData = async (id: string, data: any) => { await db.collection('users').doc(id).update({ data })}云函数调用:
// 调用云函数const callCloudFunction = async (name: string, data?: any) => { const res = await wx.cloud.callFunction({ name, data }) return res.result}// 使用示例const login = async () => { const res = await callCloudFunction('login', { code: 'xxx' }) return res}适用场景:
- 云函数:处理复杂业务逻辑、敏感操作(如支付、权限验证)
- 云数据库:存储和管理业务数据
- 云存储:上传和管理图片、文件等静态资源
性能优化进阶
Q18:小程序的性能指标有哪些?如何进行性能监控和优化?
核心性能指标:
| 指标 | 含义 | 优化目标 |
|---|---|---|
| 首次渲染时间 | 页面从加载到首次渲染完成的时间 | < 1.5s |
| 首次内容绘制 | 页面首个内容像素渲染的时间 | < 1s |
| 最大绘制时间 | 页面最大渲染帧的耗时 | < 16ms |
| JS 执行时间 | JavaScript 代码执行耗时 | < 1s |
| 内存占用 | 小程序运行时的内存使用 | < 256MB |
性能监控方案:
// 使用 performance APIconst start = performance.now()// 执行代码await fetchData()const end = performance.now()console.log('数据加载耗时:', end - start)// 监控内存const memoryInfo = wx.getMemoryInfo?.()if (memoryInfo) { console.log('内存占用:', memoryInfo.usedJSHeapSize)}优化策略:
- 代码分割:按需加载非核心代码
- 懒加载:图片、组件按需渲染
- 缓存策略:数据缓存、页面缓存
- 减少通信:合并 setData、减少跨线程通信
- 优化算法:使用更高效的数据结构和算法
Q19:如何优化 UniApp 小程序的首屏加载速度?
首屏优化方案:
// 1. 预加载关键数据onLaunch() { // 提前加载用户信息 this.preloadUserInfo()}// 2. 骨架屏<template> <view v-if="loading" class="skeleton"> <view class="skeleton-item"></view> <view class="skeleton-item"></view> </view> <view v-else> <!-- 真实内容 --> </view></template>// 3. 异步加载import { defineAsyncComponent } from 'vue'const HeavyComponent = defineAsyncComponent(() => import('./HeavyComponent.vue'))优化措施:
| 措施 | 效果 |
|---|---|
| 分包加载 | 减小主包体积,加快首屏加载 |
| 资源压缩 | 压缩 JS/CSS/图片 |
| 懒加载组件 | 非首屏组件按需加载 |
| 数据预取 | 在 onLaunch 中提前请求数据 |
| 骨架屏 | 提升用户感知体验 |
实战高级问题
Q20:如何在 UniApp 中实现微信支付和分享功能?需要注意哪些安全问题?
微信支付实现:
const pay = async (orderId: string) => { try { // 1. 后端生成支付参数 const payParams = await request({ url: '/api/pay/create', method: 'POST', data: { orderId } }) // 2. 调用微信支付 const res = await uni.requestPayment({ provider: 'wxpay', timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, signType: 'MD5', paySign: payParams.paySign }) if (res.errMsg === 'requestPayment:ok') { uni.showToast({ title: '支付成功', icon: 'success' }) } } catch (e) { uni.showToast({ title: '支付失败', icon: 'none' }) }}微信分享实现:
onLoad() { // #ifdef MP-WEIXIN wx.showShareMenu({ withShareTicket: true, menus: ['shareAppMessage', 'shareTimeline'] }) wx.onShareAppMessage(() => ({ title: '分享标题', path: '/pages/index/index', imageUrl: '/static/share.png' })) // #endif}安全注意事项:
- 支付签名:签名必须在服务端生成,禁止客户端生成
- 订单校验:支付成功后必须验证订单状态
- 防重放攻击:使用 nonce 和 timestamp 防止重复请求
- 数据加密:敏感数据传输使用 HTTPS
- 权限控制:分享内容需符合平台规范
十、注意事项
- 平台差异:不同平台的 API 支持程度不同,需做好兼容
- 性能监控:关注小程序的性能指标,及时优化
- 调试工具:使用 HBuilderX 和微信开发者工具进行调试
- 发布流程:不同平台的发布流程不同,需分别配置
十一、总结
UniApp 是一个强大的跨平台开发框架,通过 Vue 语法实现一次开发多端运行。掌握其核心概念、生命周期、API 使用和性能优化技巧,是成为优秀 UniApp 开发者的关键。