uni-app 开发实战:从入门到跨平台应用开发

一、uni-app 框架概述

uni-app 是 DCloud 公司推出的跨平台开发框架,其最大的特点是 "一次编写,多端运行"。不同于传统的跨平台方案,uni-app 并非简单的网页封装,而是通过编译器将 Vue 代码转换为各平台的原生代码,实现了接近原生应用的性能体验。

1.1 核心优势

  • 跨平台能力:支持编译到 10+ 平台,包括 iOS、Android、微信小程序、H5 等
  • 性能优化:采用原生渲染技术,部分场景下性能接近原生应用
  • 生态丰富:拥有庞大的插件市场和组件市场,支持原生插件扩展
  • 开发效率:使用 Vue.js 语法,学习成本低,开发效率高
  • 条件编译:通过特殊语法实现平台差异化逻辑

1.2 应用场景

uni-app 适用于以下场景:

  • 企业级多端应用(APP + 小程序 + H5 统一开发)
  • 快速迭代的创业项目(减少开发成本)
  • 内容型应用(博客、新闻、电商等)
  • 需要多平台覆盖的营销活动页面

二、快速入门:从环境搭建到第一个应用

2.1 开发环境准备

  1. 安装 HBuilderX:uni-app 推荐使用 DCloud 开发的 HBuilderX 编辑器,下载地址:HBuilderX-高效极客技巧

  2. Node.js 环境:确保已安装 Node.js(建议 v14+)

  3. 配置运行环境

    • 手机调试:安装 HBuilderX 内置的模拟器或连接真实手机
    • 小程序调试:下载对应平台的开发者工具(如微信开发者工具)

2.2 创建第一个 uni-app 项目

  1. 在 HBuilderX 中点击 "文件" -> "新建" -> "项目"
  2. 选择 "uni-app" 模板,输入项目名称和路径
  3. 项目结构说明:

plaintext

my-uni-app/
├── pages/              # 页面文件
├── static/             # 静态资源
├── uni_modules/        # 组件模块
├── App.vue             # 应用入口
├── main.js             # 主逻辑
├── manifest.json       # 应用配置
└── pages.json          # 页面路由配置

2.3 编写第一个页面

在 pages/index/index.vue 中编写一个简单的问候页面:

vue

<template>
  <view class="container">
    <view class="header">
      <text class="title">欢迎使用 uni-app</text>
    </view>
    <view class="content">
      <image class="logo" src="/static/logo.png"></image>
      <text class="text-area">
        <text class="text">{{ message }}</text>
      </text-area>
      <view class="platform-info">
        <text>当前平台: {{ platform }}</text>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      message: 'Hello uni-app!',
      platform: ''
    }
  },
  onLoad() {
    // 获取当前运行平台
    this.platform = uni.getSystemInfoSync().platform;
  }
}
</script>

<style>
.container {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100vh;
  padding: 20rpx;
  box-sizing: border-box;
}
.header {
  width: 100%;
  text-align: center;
  margin-bottom: 40rpx;
}
.title {
  font-size: 48rpx;
  font-weight: bold;
  color: #3A7BD5;
}
.content {
  display: flex;
  flex-direction: column;
  align-items: center;
}
.logo {
  width: 200rpx;
  height: 200rpx;
  margin: 30rpx 0;
}
.text-area {
  display: flex;
  justify-content: center;
  margin-top: 20rpx;
  width: 100%;
}
.text {
  font-size: 32rpx;
  color: #666;
}
.platform-info {
  margin-top: 30rpx;
  font-size: 28rpx;
  color: #999;
}
</style>

2.4 运行与调试

  1. 点击 HBuilderX 工具栏中的 "运行" 按钮
  2. 选择运行目标(如 "运行到手机模拟器" 或 "运行到微信开发者工具")
  3. 查看效果:应用会根据选择的平台编译并运行,显示当前平台信息

三、uni-app 核心功能与开发技巧

3.1 跨平台开发核心 - 条件编译

uni-app 通过 #ifdef#ifndef#endif 实现平台差异化逻辑:

vue

<script>
export default {
  onLoad() {
    // #ifdef APP-PLUS
    // 仅在 App 平台执行的代码
    this.initAppFeatures();
    // #endif
    
    // #ifdef H5
    // 仅在 H5 平台执行的代码
    this.initH5Features();
    // #endif
    
    // #ifdef MP
    // 仅在小程序平台执行的代码
    this.initMiniProgramFeatures();
    // #endif
  }
}
</script>

3.2 原生能力调用

uni-app 封装了丰富的原生 API,以下是调用相机拍照的示例:

javascript

// 调用相机拍照
uni.chooseImage({
  count: 1,
  sourceType: ['camera'],
  success: (res) => {
    const tempFilePaths = res.tempFilePaths;
    uni.showModal({
      title: '拍照成功',
      content: '图片路径: ' + tempFilePaths[0]
    });
    
    // 上传图片到服务器
    uni.uploadFile({
      url: 'https://your-server.com/upload',
      filePath: tempFilePaths[0],
      name: 'file',
      success: (uploadRes) => {
        const data = JSON.parse(uploadRes.data);
        uni.showToast({
          title: '上传成功',
          icon: 'success'
        });
      }
    });
  }
});

3.3 组件化开发

uni-app 支持 Vue 组件系统,可以创建复用组件:

vue

<!-- components/button/index.vue -->
<template>
  <button 
    :class="['custom-button', type === 'primary' ? 'primary' : type === 'secondary' ? 'secondary' : '']"
    :disabled="disabled"
    @click="handleClick"
  >
    <slot></slot>
  </button>
</template>

<script>
export default {
  props: {
    type: {
      type: String,
      default: 'default'
    },
    disabled: {
      type: Boolean,
      default: false
    }
  },
  methods: {
    handleClick() {
      if (!this.disabled) {
        this.$emit('click');
      }
    }
  }
}
</script>

<style>
.custom-button {
  padding: 16rpx 40rpx;
  border-radius: 8rpx;
  font-size: 28rpx;
  font-weight: normal;
}
.primary {
  background-color: #3A7BD5;
  color: #FFFFFF;
}
.secondary {
  background-color: #F2F2F2;
  color: #333333;
  border: 1rpx solid #DDDDDD;
}
</style>

使用组件:

vue

<template>
  <view>
    <custom-button type="primary" @click="submitForm">提交表单</custom-button>
    <custom-button type="secondary" @click="cancelForm">取消</custom-button>
  </view>
</template>

<script>
import CustomButton from '@/components/button/index.vue'

export default {
  components: {
    CustomButton
  },
  methods: {
    submitForm() {
      uni.showToast({
        title: '表单提交成功',
        icon: 'success'
      });
    },
    cancelForm() {
      uni.showModal({
        title: '提示',
        content: '确定要取消吗?',
        success: (res) => {
          if (res.confirm) {
            uni.navigateBack();
          }
        }
      });
    }
  }
}
</script>

四、实战案例:开发跨平台 TODO 应用

4.1 项目规划

我们将开发一个 TODO 应用,具备以下功能:

  • 任务列表展示
  • 新增任务
  • 完成 / 取消完成任务
  • 删除任务
  • 任务筛选(全部 / 进行中 / 已完成)

4.2 数据模型设计

javascript

// models/todo.js
export default {
  id: '', // 任务ID
  title: '', // 任务标题
  description: '', // 任务描述
  isCompleted: false, // 是否完成
  createTime: '', // 创建时间
  dueTime: '' // 截止时间
}

4.3 页面开发

首页 (pages/todo/index.vue)

vue

<template>
  <view class="todo-container">
    <!-- 头部标题 -->
    <view class="header">
      <text class="title">我的待办事项</text>
    </view>
    
    <!-- 新增任务 -->
    <view class="add-todo">
      <input 
        v-model="newTodoTitle" 
        placeholder="添加新任务..." 
        class="todo-input"
        @keyup.enter="addTodo"
      >
      <button class="add-button" @click="addTodo">+</button>
    </view>
    
    <!-- 任务统计 -->
    <view class="stats">
      <text>共有 {{ totalTodos }} 项任务,已完成 {{ completedTodos }} 项</text>
    </view>
    
    <!-- 任务筛选 -->
    <view class="filter">
      <button 
        class="filter-btn" 
        :class="{ active: filter === 'all' }"
        @click="filter = 'all'"
      >全部</button>
      <button 
        class="filter-btn" 
        :class="{ active: filter === 'active' }"
        @click="filter = 'active'"
      >进行中</button>
      <button 
        class="filter-btn" 
        :class="{ active: filter === 'completed' }"
        @click="filter = 'completed'"
      >已完成</button>
    </view>
    
    <!-- 任务列表 -->
    <view class="todo-list">
      <view 
        v-for="todo in filteredTodos" 
        :key="todo.id" 
        class="todo-item"
      >
        <view class="todo-content">
          <checkbox 
            :checked="todo.isCompleted" 
            @change="toggleTodoStatus(todo)"
            class="todo-checkbox"
          >
          </checkbox>
          <view class="todo-text" :class="{ completed: todo.isCompleted }">
            <text class="todo-title">{{ todo.title }}</text>
            <text v-if="todo.description" class="todo-desc">{{ todo.description }}</text>
          </view>
        </view>
        <button class="delete-btn" @click="deleteTodo(todo)">
          <text>删除</text>
        </button>
      </view>
      
      <!-- 空状态提示 -->
      <view v-if="filteredTodos.length === 0" class="empty-state">
        <text>暂无待办任务,点击添加按钮创建任务</text>
      </view>
    </view>
  </view>
</template>

<script>
import TodoService from '@/services/todo.js'

export default {
  data() {
    return {
      newTodoTitle: '',
      newTodoDesc: '',
      filter: 'all',
      todos: []
    }
  },
  computed: {
    totalTodos() {
      return this.todos.length;
    },
    completedTodos() {
      return this.todos.filter(todo => todo.isCompleted).length;
    },
    filteredTodos() {
      if (this.filter === 'all') {
        return this.todos;
      } else if (this.filter === 'active') {
        return this.todos.filter(todo => !todo.isCompleted);
      } else if (this.filter === 'completed') {
        return this.todos.filter(todo => todo.isCompleted);
      }
      return this.todos;
    }
  },
  onLoad() {
    this.loadTodos();
  },
  methods: {
    // 加载任务列表
    loadTodos() {
      TodoService.getTodos().then(todos => {
        this.todos = todos;
      }).catch(err => {
        uni.showToast({
          title: '加载失败',
          icon: 'none'
        });
        console.error(err);
      });
    },
    
    // 添加任务
    addTodo() {
      if (!this.newTodoTitle.trim()) {
        uni.showToast({
          title: '任务标题不能为空',
          icon: 'none'
        });
        return;
      }
      
      const newTodo = {
        id: new Date().getTime().toString(),
        title: this.newTodoTitle.trim(),
        description: this.newTodoDesc,
        isCompleted: false,
        createTime: new Date().toISOString(),
        dueTime: ''
      };
      
      TodoService.addTodo(newTodo).then(() => {
        this.loadTodos();
        this.newTodoTitle = '';
        this.newTodoDesc = '';
      }).catch(err => {
        uni.showToast({
          title: '添加失败',
          icon: 'none'
        });
        console.error(err);
      });
    },
    
    // 切换任务状态
    toggleTodoStatus(todo) {
      todo.isCompleted = !todo.isCompleted;
      TodoService.updateTodo(todo).catch(err => {
        uni.showToast({
          title: '更新失败',
          icon: 'none'
        });
        console.error(err);
        todo.isCompleted = !todo.isCompleted; // 回滚状态
      });
    },
    
    // 删除任务
    deleteTodo(todo) {
      uni.showModal({
        title: '确认删除',
        content: '确定要删除这个任务吗?',
        success: (res) => {
          if (res.confirm) {
            TodoService.deleteTodo(todo.id).then(() => {
              this.loadTodos();
            }).catch(err => {
              uni.showToast({
                title: '删除失败',
                icon: 'none'
              });
              console.error(err);
            });
          }
        }
      });
    }
  }
}
</script>

<style>
.todo-container {
  padding: 30rpx;
  box-sizing: border-box;
}
.header {
  text-align: center;
  margin-bottom: 40rpx;
}
.title {
  font-size: 48rpx;
  font-weight: bold;
  color: #333;
}
.add-todo {
  display: flex;
  margin-bottom: 30rpx;
}
.todo-input {
  flex: 1;
  height: 88rpx;
  background-color: #F5F5F5;
  border-radius: 44rpx;
  padding: 0 30rpx;
  font-size: 28rpx;
}
.add-button {
  width: 88rpx;
  height: 88rpx;
  line-height: 88rpx;
  border-radius: 50%;
  background-color: #3A7BD5;
  color: #FFFFFF;
  margin-left: 20rpx;
  padding: 0;
  font-size: 40rpx;
}
.stats {
  font-size: 24rpx;
  color: #999;
  margin-bottom: 30rpx;
}
.filter {
  display: flex;
  margin-bottom: 30rpx;
  border-bottom: 1rpx solid #EEEEEE;
  padding-bottom: 20rpx;
}
.filter-btn {
  flex: 1;
  height: 80rpx;
  line-height: 80rpx;
  padding: 0;
  font-size: 28rpx;
  color: #666;
  background-color: transparent;
}
.filter-btn.active {
  color: #3A7BD5;
  font-weight: bold;
  position: relative;
}
.filter-btn.active::after {
  content: '';
  position: absolute;
  bottom: 0;
  left: 50%;
  transform: translateX(-50%);
  width: 40rpx;
  height: 6rpx;
  background-color: #3A7BD5;
  border-radius: 3rpx;
}
.todo-list {
  width: 100%;
}
.todo-item {
  display: flex;
  align-items: center;
  padding: 30rpx 0;
  border-bottom: 1rpx solid #EEEEEE;
}
.todo-item:last-child {
  border-bottom: none;
}
.todo-checkbox {
  margin-right: 20rpx;
}
.todo-content {
  flex: 1;
  display: flex;
  align-items: flex-start;
}
.todo-text {
  flex: 1;
}
.todo-title {
  font-size: 28rpx;
  color: #333;
  display: block;
  margin-bottom: 10rpx;
}
.todo-desc {
  font-size: 24rpx;
  color: #999;
  display: block;
}
.todo-text.completed .todo-title,
.todo-text.completed .todo-desc {
  color: #CCCCCC;
  text-decoration: line-through;
}
.delete-btn {
  width: 80rpx;
  height: 80rpx;
  line-height: 80rpx;
  padding: 0;
  font-size: 24rpx;
  color: #F56C6C;
  background-color: transparent;
}
.empty-state {
  text-align: center;
  padding: 60rpx 0;
  color: #999;
  font-size: 28rpx;
}
</style>

4.4 数据服务实现

javascript

// services/todo.js
export default {
  // 获取任务列表
  getTodos() {
    return new Promise((resolve, reject) => {
      // 从本地存储获取
      const todos = uni.getStorageSync('todos');
      if (todos) {
        resolve(todos);
      } else {
        // 初始化空列表
        uni.setStorageSync('todos', []);
        resolve([]);
      }
    });
  },
  
  // 添加任务
  addTodo(todo) {
    return new Promise((resolve, reject) => {
      this.getTodos().then(todos => {
        todos.unshift(todo);
        uni.setStorageSync('todos', todos);
        resolve(todo);
      }).catch(reject);
    });
  },
  
  // 更新任务
  updateTodo(todo) {
    return new Promise((resolve, reject) => {
      this.getTodos().then(todos => {
        const index = todos.findIndex(item => item.id === todo.id);
        if (index !== -1) {
          todos[index] = todo;
          uni.setStorageSync('todos', todos);
          resolve(todo);
        } else {
          reject(new Error('任务不存在'));
        }
      }).catch(reject);
    });
  },
  
  // 删除任务
  deleteTodo(id) {
    return new Promise((resolve, reject) => {
      this.getTodos().then(todos => {
        const newTodos = todos.filter(todo => todo.id !== id);
        uni.setStorageSync('todos', newTodos);
        resolve(newTodos);
      }).catch(reject);
    });
  }
}

五、uni-app 开发中的挑战与解决方案

5.1 平台差异处理

虽然 uni-app 提供了跨平台能力,但各平台仍有差异,常见解决方案:

  1. 条件编译:通过 #ifdef 等预处理指令实现平台差异化逻辑
  2. 组件封装:对平台特有组件进行封装,提供统一接口
  3. 插件扩展:使用 uni-app 原生插件市场的插件解决特殊需求

5.2 性能优化

  1. 图片优化:使用 uni.createSelectorQuery() 实现图片懒加载
  2. 长列表优化:使用 scroll-view 结合虚拟列表技术
  3. 减少重绘:合理使用 v-show 与 v-if,避免频繁切换
  4. 本地存储优化:批量操作本地存储,减少 IO 次数

5.3 小程序平台限制

小程序平台有独特的限制,如:

  • 页面层级限制(通常不超过 10 层)
  • 包大小限制(通常不超过 2MB)
  • 运行环境限制(无浏览器完整 DOM API)
  • 分包加载:将应用拆分为多个包
  • 组件化设计:提高代码复用率
  • 优化资源:压缩图片、代码丑化等

六、uni-app 生态与未来发展

6.1 生态系统

uni-app 拥有丰富的生态系统:

  • 插件市场:超过 10,000+ 原生插件,覆盖各类功能需求
  • 组件市场:大量可复用的 UI 组件和业务组件
  • 官方文档:详细的开发文档和示例
  • 社区支持:DCloud 官方社区、微信社群、掘金 / 知乎等技术社区

6.2 未来发展趋势

uni-app 正在不断进化,未来发展方向包括:

  • 性能提升:进一步优化渲染引擎,接近原生应用性能
  • 跨平台能力扩展:支持更多新兴平台和设备
  • 开发体验优化:完善工具链,提升开发效率
  • 生态繁荣:吸引更多开发者和企业加入生态建设

七、总结

uni-app 为开发者提供了一个高效的跨平台解决方案,通过一套代码实现多端部署,大大降低了开发成本和维护成本。无论是创业团队、企业应用还是个人开发者,uni-app 都是一个值得考虑的选择。

通过本文的介绍,你已经了解了 uni-app 的基本概念、开发流程和实战技巧。接下来,建议你动手实践,从简单的应用开始,逐步掌握这一强大的跨平台开发框架。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值