UniApp跨平台开发指南

UniApp跨平台开发指南

从零开始:UniApp 跨平台开发实战与避坑指南

在跨平台开发领域,UniApp 凭借其“一套代码,多端发布”的能力,已成为众多开发者的首选方案。本文将结合实战经验,分享 UniApp 的核心开发技巧与常见问题解决方案。

一、为什么选择 UniApp?

UniApp 基于 Vue.js 语法,支持编译到 iOS、Android、H5、以及各类小程序平台。它的核心优势包括:

  • 高效开发:一份代码,多端复用
  • 性能接近原生:经过编译优化,体验优于传统 WebView 方案
  • 插件生态丰富:DCloud 插件市场提供大量现成组件和模块
  • 社区活跃:遇到问题更容易找到解决方案

二、项目初始化与目录规划

2.1 创建项目

使用官方 CLI 创建项目(推荐,更灵活):

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

2.2 推荐的目录结构

src/
├── pages/          # 页面目录
├── components/     # 公共组件
├── static/         # 静态资源
├── utils/          # 工具函数
├── api/            # 接口封装
├── store/          # 状态管理(Vuex/Pinia)
├── styles/         # 全局样式
├── manifest.json   # 应用配置
├── pages.json      # 页面路由配置
└── App.vue         # 应用入口

三、核心开发实践

3.1 条件编译处理多端差异

UniApp 最强大的特性之一就是条件编译,可以针对不同平台编写特定代码:

<template>
  <view>
    <!-- #ifdef MP-WEIXIN -->
    <button open-type="getUserInfo">微信登录</button>
    <!-- #endif -->

    <!-- #ifdef APP-PLUS -->
    <button @click="appLogin">App登录</button>
    <!-- #endif -->
  </view>
</template>

<script>
export default {
  methods: {
    getSystemInfo() {
      // #ifdef APP-PLUS
      plus.device.getInfo((info) => {
        console.log('App设备信息:', info)
      })
      // #endif

      // #ifdef MP-WEIXIN
      wx.getSystemInfo({
        success: (res) => console.log('微信设备信息:', res)
      })
      // #endif
    }
  }
}
</script>

<style>
/* #ifdef H5 */
.container {
  width: 100vw;
}
/* #endif */

/* #ifdef APP-PLUS */
.container {
  width: 100%;
}
/* #endif */
</style>

3.2 路由与页面跳转

UniApp 使用 pages.json 统一管理路由:

{
  "pages": [
    {
      "path": "pages/index/index",
      "style": {
        "navigationBarTitleText": "首页",
        "enablePullDownRefresh": true
      }
    },
    {
      "path": "pages/detail/detail",
      "style": {
        "navigationBarTitleText": "详情页"
      }
    }
  ],
  "globalStyle": {
    "navigationBarTextStyle": "black",
    "navigationBarBackgroundColor": "#FFFFFF"
  }
}

页面跳转方法:

// 普通跳转(保留当前页面)
uni.navigateTo({
  url: '/pages/detail/detail?id=123'
})

// 重定向(关闭当前页面)
uni.redirectTo({
  url: '/pages/login/login'
})

// Tab 切换
uni.switchTab({
  url: '/pages/user/user'
})

// 返回上一页
uni.navigateBack({
  delta: 1
})

3.3 网络请求封装

建议对 uni.request 进行统一封装:

// utils/request.js
const BASE_URL = 'https://api.example.com'

const request = (options) => {
  return new Promise((resolve, reject) => {
    uni.showLoading({ title: '加载中...' })

    uni.request({
      url: BASE_URL + options.url,
      method: options.method || 'GET',
      data: options.data || {},
      header: {
        'Content-Type': 'application/json',
        'Authorization': uni.getStorageSync('token') || ''
      },
      success: (res) => {
        if (res.statusCode === 200) {
          resolve(res.data)
        } else if (res.statusCode === 401) {
          // token 失效,跳转登录
          uni.navigateTo({ url: '/pages/login/login' })
          reject(res)
        } else {
          uni.showToast({
            title: res.data.message || '请求失败',
            icon: 'none'
          })
          reject(res)
        }
      },
      fail: (err) => {
        uni.showToast({
          title: '网络异常,请稍后重试',
          icon: 'none'
        })
        reject(err)
      },
      complete: () => {
        uni.hideLoading()
      }
    })
  })
}

export default request

3.4 状态管理(Pinia)

UniApp 官方推荐使用 Pinia 替代 Vuex:

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

export const useUserStore = defineStore('user', {
  state: () => ({
    token: '',
    userInfo: null
  }),

  getters: {
    isLogin: (state) => !!state.token,
    userName: (state) => state.userInfo?.nickName || '游客'
  },

  actions: {
    setToken(token) {
      this.token = token
      uni.setStorageSync('token', token)
    },

    async login(phone, code) {
      const res = await api.login({ phone, code })
      this.setToken(res.token)
      this.userInfo = res.userInfo
      return res
    },

    logout() {
      this.token = ''
      this.userInfo = null
      uni.removeStorageSync('token')
      uni.clearStorageSync()
    }
  }
})

四、常见问题与解决方案

4.1 样式兼容性问题

问题:不同平台对 CSS 属性的支持程度不同。

解决方案

/* 使用 rpx 作为基本单位,UniApp 会自动转换 */
.container {
  width: 750rpx;
  padding: 20rpx;
}

/* 针对特定平台覆盖样式 */
/* #ifdef H5 */
.container {
  max-width: 750px;
  margin: 0 auto;
}
/* #endif */

/* #ifdef APP-PLUS */
.container {
  box-shadow: 0 2rpx 10rpx rgba(0,0,0,0.1);
}
/* #endif */

4.2 图片处理

<template>
  <view>
    <!-- 网络图片需要配置域名白名单 -->
    <image :src="imageUrl" mode="aspectFill"></image>

    <!-- 静态图片推荐放在 static 目录 -->
    <image src="/static/logo.png"></image>

    <!-- 大图懒加载 -->
    <image 
      v-for="(img, idx) in imageList" 
      :key="idx"
      :src="img"
      lazy-load
    ></image>
  </view>
</template>

4.3 小程序端常见限制

// 1. 避免使用 eval、new Function 等动态执行代码
// ❌ 错误示例
const fn = new Function('a', 'b', 'return a + b')

// 2. 页面层级限制为 10 层,注意避免无限跳转
// 可使用 getCurrentPages() 检查当前页面栈
const pages = getCurrentPages()
if (pages.length >= 9) {
  uni.redirectTo({ url: targetUrl })  // 使用 redirectTo 替代 navigateTo
}

// 3. 分包加载优化
// 在 pages.json 中配置分包
{
  "subPackages": [{
    "root": "packageA",
    "pages": [
      "pages/cat/cat",
      "pages/dog/dog"
    ]
  }]
}

4.4 性能优化建议

// 1. 使用 v-if 替代 v-show 减少渲染开销(小程序端)

// 2. 长列表使用虚拟滚动
// 推荐使用插件市场的 recycle-view 组件

// 3. 图片压缩与 WebP 格式
// 4. 减少页面 data 数据量
// 5. 避免在 onPageScroll 中执行复杂计算

// 6. 使用 uni.preloadPage 预加载
uni.preloadPage({
  url: '/pages/detail/detail'
})

五、调试与发布

5.1 调试技巧

  • H5 端:使用 Chrome DevTools
  • App 端:使用 uni-app 调试器 + Android Studio / Xcode
  • 小程序端:使用对应平台的开发者工具

5.2 发布命令

# 发布到 H5
npm run build:h5

# 发布到微信小程序
npm run build:mp-weixin

# 发布到 App(需要 HBuilderX 云打包)
# 或使用命令行打包
npm run build:app

六、写在最后

UniApp 虽然简化了跨平台开发,但仍需理解各平台特性差异。建议在开发初期就确定目标平台,合理使用条件编译,并时刻关注性能问题。

如果你正在开始一个新的跨平台项目,UniApp 绝对值得考虑。希望本文能帮助你少走弯路,顺利交付多端应用。

本文首发于技术博客,欢迎交流讨论。如有错误或补充,请留言指正。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享
评论 抢沙发

请登录后发表评论

    暂无评论内容