乐于分享
好东西不私藏

Vue3 转 UniApp 必看!TS+Composition API 全实战,6 大避坑 + 完整源码

Vue3 转 UniApp 必看!TS+Composition API 全实战,6 大避坑 + 完整源码

Vue3 玩得溜,一上手 UniApp 就懵?生命周期分不清、页面传参摸不透、axios 用不了、Pinia 不知道怎么配、本地存储总踩坑……

如果你是Vue3+TS 开发者,想快速搞定 UniApp 跨平台开发,这篇实战指南帮你一次性扫清所有障碍,从环境搭建到完整商城 Demo,直接抄作业就能用!

一、🔥 快速上手:TS 版 UniApp 项目搭建

1.1 一键创建项目

命令行快速初始化 Vue3+Vite+TS 项目:

npx degit dcloudio/uni-preset-vue#vite-ts my-ts-app
cd my-ts-app
npm install

HBuilderX 用户:新建项目选择uni-app (Vue3) ,直接勾选启用 TypeScript即可。

1.2 TS 配置核心(tsconfig.json)

{
"compilerOptions": {
"types": ["@dcloudio/types""miniprogram-api-typings"],
"strict"true,
"moduleResolution""node",
"resolveJsonModule"true
  },
"include": ["src/**/*.ts""src/**/*.d.ts""src/**/*.vue"]
}

1.3 类型声明补全(shims-uni.d.ts)

解决 Vue 文件 TS 报错问题:

declaremodule '*.vue' {
importtype { DefineComponent } from'vue'
const component: DefineComponent<{}, {}, any>
exportdefault component
}

二、⏰ 生命周期:一张表理清 Vue3 vs UniApp

很多人最懵的就是生命周期,直接看对比表,一秒看懂差异👇

场景
Vue3 组件生命周期
UniApp 页面生命周期
核心说明
创建
setup()
setup()
同步执行,无 DOM
DOM 挂载
onMounted
onReady
页面 DOM 渲染完成用 onReady
页面显示
onShow
每次进入页面必触发
页面隐藏
onHide
切后台 / 跳转页面触发
页面卸载
onUnmounted
onUnload
销毁页面,清理资源
初次加载
onLoad
只执行一次,接收路由参数

✅ 组合式 API 代码示例

<scriptsetuplang="ts">
import { onMounted, ref } from'vue'
import { onLoad, onShow, onReady, onHide, onUnload } from'@dcloudio/uni-app'

const id = ref('')

// 只执行一次,接收参数
onLoad((options: any) => {
  id.value = options.id
})
// 每次进入页面触发
onShow(() => {})
// DOM渲染完成,可获取元素位置
onReady(() => {})
// 页面隐藏
onHide(() => {})
// 页面卸载,清理定时器/监听
onUnload(() => {})
</script>

⚠️ 致命坑点

  • ❌ onLoad 中绝对不能获取 DOM,此时页面未渲染
  • ❌ 页面卸载必须清理定时器、事件监听,否则内存泄漏

三、🚀 页面路由 & 传参:告别 Vue Router 思维

UniApp 没有$router/$route,全套用uni API,3 种传参方式直接用👇

3.1 URL 传参(最常用)

发送页:特殊字符必须编码

const name = encodeURIComponent('张三&李四')
uni.navigateTo({
  url: `/pages/detail/detail?id=123&name=${name}`
})

接收页:onLoad 自动解码

onLoad((options: { id: string; name: string }) => {
// 直接获取,无需手动解码
console.log(options.id, options.name)
})

3.2 事件总线(跨页面通信)

发送页

uni.$emit('updateCart', { count: 5 })
uni.navigateBack()

接收页:必须成对监听 & 移除

const handleUpdate = (data: { count: number }) => {}

onLoad(() => {
  uni.$on('updateCart', handleUpdate)
})
onUnload(() => {
  uni.$off('updateCart', handleUpdate) // 必写!
})

3.3 与 Vue Router 核心差异

  • $router:跳转用uni.navigateTo/redirectTo/switchTab
  • $route:参数全靠onLoad(options)
  • 无嵌套路由:页面全扁平化,配置pages.json

四、🌐 网络请求:抛弃 axios,封装 uni.request+TS

UniApp 不支持 axios(依赖 XHR),uni.request才是正统,封装成 Promise+TS 更优雅👇

4.1 通用请求封装(utils/request.ts)

interface RequestOptions {
  url: string
  method?: 'GET'|'POST'|'PUT'|'DELETE'
  data?: any
  header?: any
  showLoading?: boolean
}

exportfunctionrequest<T = any>(options: RequestOptions): Promise<T{
returnnewPromise((resolve, reject) => {
    options.showLoading && uni.showLoading({ title: '加载中...' })
    uni.request({
      ...options,
      success: (res) => {
        res.statusCode === 200 ? resolve(res.data as T) : reject(res)
      },
      complete: () => {
        uni.hideLoading()
      }
    })
  })
}

4.2 类型安全 API 调用

// api/user.ts
interface User { id: number; name: string }
exportconst getUserInfo = (id: number) => {
return request<User>({ url: `/user/${id}` })
}

// 页面使用
const user = await getUserInfo(123)
// TS自动推断类型,智能提示拉满
console.log(user.name)

五、💾 本地存储:别再用 localStorage!

UniApp 有专属存储 API,自动序列化对象,比 localStorage 强太多👇

功能
Web localStorage
UniApp API
核心差异
同步存
setItem
setStorageSync
自动转对象
同步取
getItem
getStorageSync
自动解析对象
异步存
setStorage
支持异步
容量
5-10MB
小程序 10MB
小程序有限制

✅ 代码示例

// 存对象(无需JSON.stringify)
uni.setStorageSync('user', { name: '张三', age: 18 })
// 取对象(无需JSON.parse)
const user = uni.getStorageSync('user'as { name: string; age: number } | null
// 删/清空
uni.removeStorageSync('user')
uni.clearStorageSync()

六、🧩 状态管理:Pinia 完美适配 UniApp

Pinia 轻量易用,完全兼容 UniApp,只需自己做持久化👇

6.1 安装 & 全局注册

npm install pinia
// main.ts
import { createSSRApp } from'vue'
import App from'./App.vue'
import { createPinia } from'pinia'

exportfunctioncreateApp() {
const app = createSSRApp(App)
  app.use(createPinia())
return { app }
}

6.2 定义 Store + 持久化

// stores/user.ts
import { defineStore } from'pinia'

exportconst useUserStore = defineStore('user', {
  state: () => ({
    token: '',
    userInfo: nullas { name: string } | null
  }),
  getters: { isLogin: s => !!s.token },
  actions: {
    login(token: string, userInfo: any) {
this.token = token
this.userInfo = userInfo
// 持久化到本地存储
      uni.setStorageSync('userStore', { token, userInfo })
    },
    init() {
// 启动时读取本地数据
const data = uni.getStorageSync('userStore')
      data && Object.assign(this, data)
    }
  }
})

七、❌ 6 大高频坑点 + 解决方案

开发中 90% 的报错都在这,直接对照解决👇

  1. onLoad 获取 DOM 为 0 → 移到 onReady/nextTick
  2. TS 找不到 @dcloudio/uni-app → 安装@dcloudio/types并配置 tsconfig
  3. Pinia 数据不更新 → 不要把 store 值复制到本地 data
  4. 存储对象类型丢失 → 使用类型断言as User | null
  5. 事件总线重复触发 → onUnload 必须uni.$off
  6. URL 传参对象丢失 → 用JSON.stringify+encodeURIComponent

八、🛒 完整实战:商城小程序 Demo

直接套用以下结构,快速开发上线👇

src/
├── api/          # 接口请求
├── components/   # 公共组件
├── pages/        # 页面(首页/登录/详情)
├── stores/       # Pinia状态
├── utils/        # 工具类
└── pages.json    # 路由配置

核心代码亮点

  • 商品列表下拉加载更多
  • 类型安全的商品接口定义
  • 用户登录状态持久化
  • 页面跳转 & 参数传递

九、✨ 最后总结

从 Vue3 切换 UniApp,核心是理解平台差异,而非照搬 Web 开发逻辑:

  • 生命周期用onReady替代 onMounted 操作 DOM
  • 路由传参靠onLoad,放弃 Vue Router
  • 请求用uni.request,存储用uni.setStorageSync
  • Pinia 正常用,记得配合uni 存储做持久化

吃透这篇,你写 UniApp 就和写普通 Vue3 一样丝滑~