第242集:Vue 3 Taro多端开发深度指南
概述
Taro是一个开放式跨端跨框架解决方案,支持使用React/Vue/Nerv等框架来开发微信小程序、H5、React Native等应用。本集将深入探讨Taro框架的核心概念、项目结构、页面开发、组件开发、API调用以及跨平台适配等内容,帮助你掌握Taro多端开发的深度使用技巧。
一、Taro框架核心概念
1.1 Taro的设计理念
- 开放式跨端跨框架:支持React、Vue、Nerv等多种前端框架
- 编译时框架:通过编译将源代码转换为各平台可执行的代码
- 组件化开发:采用组件化思想,提高代码复用性
- API统一封装:提供统一的API调用方式,屏蔽平台差异
- 小程序优先:基于小程序设计理念,同时支持多端开发
1.2 Taro支持的平台
- 小程序:微信小程序、支付宝小程序、百度小程序、头条小程序、QQ小程序、京东小程序
- H5:响应式网页
- React Native:iOS、Android原生应用
- HarmonyOS:华为HarmonyOS应用
1.3 Taro与其他跨平台框架对比
| 框架 | 特点 | 适用场景 |
|---|---|---|
| Taro | 跨框架支持、编译时方案、小程序优先 | 需要覆盖多个小程序平台的应用 |
| Uni-app | Vue语法、运行时方案、一套代码多端运行 | 熟悉Vue生态的开发者 |
| Flutter | 原生性能、Dart语言、UI一致性好 | 注重性能和UI一致性的应用 |
| React Native | JavaScript语言、原生组件、社区活跃 | 熟悉React生态的开发者 |
二、Taro环境搭建与项目创建
2.1 开发环境配置
- 安装Node.js:确保Node.js版本>=14.0.0
- 安装Taro CLI:Taro官方命令行工具
- 安装微信开发者工具:用于微信小程序预览和调试
- 安装React Native开发环境(可选):用于React Native应用开发
- 安装HarmonyOS开发环境(可选):用于HarmonyOS应用开发
2.2 安装Taro CLI
# 使用npm安装
npm install -g @tarojs/cli
# 使用yarn安装
yarn global add @tarojs/cli
# 验证安装
$ taro --version
3.6.02.3 创建Taro项目
# 创建Vue 3项目
taro init my-taro-project
# 选择框架:Vue 3
# 选择模板:默认模板
# 选择CSS预处理器:Sass/SCSS
# 选择状态管理:Pinia
# 选择编译工具:Webpack52.4 项目目录结构
├── config/ # 配置目录
│ ├── dev.js # 开发环境配置
│ ├── index.js # 默认配置
│ └── prod.js # 生产环境配置
├── src/ # 源代码目录
│ ├── app.config.ts # 应用配置
│ ├── app.vue # 应用入口组件
│ ├── components/ # 自定义组件目录
│ ├── pages/ # 页面目录
│ │ └── index/ # 首页
│ │ ├── index.config.ts # 页面配置
│ │ └── index.vue # 页面组件
│ ├── store/ # 状态管理目录
│ └── utils/ # 工具函数目录
├── package.json # 项目依赖配置
└── tsconfig.json # TypeScript配置三、Taro页面开发
3.1 页面配置
// src/pages/index/index.config.ts
export default {
navigationBarTitleText: '首页',
navigationBarBackgroundColor: '#ffffff',
navigationBarTextStyle: 'black',
enablePullDownRefresh: true,
onReachBottomDistance: 50
};3.2 页面生命周期
<template>
<view class="page-container">
<text>页面内容</text>
</view>
</template>
<script setup lang="ts">
import { onMounted, onUnmounted } from 'vue';
import { useDidShow, useDidHide, usePullDownRefresh, useReachBottom } from '@tarojs/taro';
// Vue 3 生命周期
onMounted(() => {
console.log('Vue onMounted');
});
onUnmounted(() => {
console.log('Vue onUnmounted');
});
// Taro 页面生命周期
useDidShow(() => {
console.log('页面显示');
});
useDidHide(() => {
console.log('页面隐藏');
});
usePullDownRefresh(() => {
console.log('下拉刷新');
// 停止下拉刷新
Taro.stopPullDownRefresh();
});
useReachBottom(() => {
console.log('上拉触底');
});
</script>3.3 页面路由与跳转
3.3.1 声明式导航
<!-- 普通跳转 -->
<navigator url="/pages/detail/detail">
跳转到详情页
</navigator>
<!-- 带参数跳转 -->
<navigator url="/pages/detail/detail?id=1&name=test">
跳转到详情页
</navigator>
<!-- 重定向 -->
<navigator url="/pages/login/login" open-type="redirect">
重定向到登录页
</navigator>
<!-- 切换Tab -->
<navigator url="/pages/mine/mine" open-type="switchTab">
切换到我的页面
</navigator>3.3.2 编程式导航
import Taro from '@tarojs/taro';
// 普通跳转
Taro.navigateTo({
url: '/pages/detail/detail?id=1&name=test',
success: () => {
console.log('跳转成功');
}
});
// 重定向
Taro.redirectTo({
url: '/pages/login/login'
});
// 切换Tab
Taro.switchTab({
url: '/pages/mine/mine'
});
// 关闭所有页面,打开到应用内的某个页面
Taro.reLaunch({
url: '/pages/index/index'
});
// 关闭当前页面,返回上一页面或多级页面
Taro.navigateBack({
delta: 1
});3.4 页面参数传递与接收
3.4.1 传递参数
// 方式1:URL参数
Taro.navigateTo({
url: `/pages/detail/detail?id=${id}&name=${name}`
});
// 方式2:EventChannel
Taro.navigateTo({
url: '/pages/detail/detail',
success: (res) => {
// 向被打开页面发送数据
res.eventChannel.emit('acceptDataFromOpenerPage', { data: 'test' });
}
});3.4.2 接收参数
// 方式1:通过路由参数接收
import { useRouter } from '@tarojs/taro-vue3';
const router = useRouter();
console.log('路由参数:', router.params);
// 方式2:通过EventChannel接收
import Taro from '@tarojs/taro';
const eventChannel = Taro.getCurrentInstance().eventChannel;
if (eventChannel) {
eventChannel.on('acceptDataFromOpenerPage', (data) => {
console.log('Received data:', data);
});
}四、Taro组件开发
4.1 自定义组件基础
4.1.1 创建自定义组件
<!-- src/components/CustomButton/CustomButton.vue -->
<template>
<button
class="custom-button"
:class="{ 'custom-button--primary': type === 'primary' }"
@click="handleClick"
>
<slot></slot>
</button>
</template>
<script setup lang="ts">
// 组件属性
interface Props {
type?: 'default' | 'primary';
}
const props = withDefaults(defineProps<Props>(), {
type: 'default'
});
// 组件事件
const emit = defineEmits(['click']);
const handleClick = () => {
emit('click');
};
</script>
<style scoped lang="scss">
.custom-button {
padding: 10px 20px;
border: 1px solid #ddd;
border-radius: 4px;
background-color: #fff;
&--primary {
background-color: #007aff;
color: #fff;
border-color: #007aff;
}
}
</style>4.1.2 使用自定义组件
<template>
<view class="container">
<CustomButton @click="handleButtonClick">
默认按钮
</CustomButton>
<CustomButton type="primary" @click="handleButtonClick">
主要按钮
</CustomButton>
</view>
</template>
<script setup lang="ts">
// 引入自定义组件
import CustomButton from '@/components/CustomButton/CustomButton.vue';
const handleButtonClick = () => {
console.log('Button clicked');
};
</script>4.2 全局组件注册
// src/app.vue
import { createApp } from 'vue';
import CustomButton from './components/CustomButton/CustomButton.vue';
const app = createApp(App);
// 注册全局组件
app.component('CustomButton', CustomButton);
app.mount('#app');4.3 组件通信
4.3.1 Props与事件
<!-- 父组件 -->
<template>
<view class="container">
<ChildComponent
:message="parentMessage"
@update:message="parentMessage = $event"
></ChildComponent>
</view>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const parentMessage = ref('Hello from parent');
</script>
<!-- 子组件 -->
<template>
<view class="child-container">
<text>{{ message }}</text>
<button @click="updateMessage">更新消息</button>
</view>
</template>
<script setup lang="ts">
interface Props {
message: string;
}
const props = defineProps<Props>();
const emit = defineEmits(['update:message']);
const updateMessage = () => {
emit('update:message', 'Hello from child');
};
</script>4.3.2 依赖注入(Provide/Inject)
<!-- 父组件 -->
<template>
<view class="container">
<ChildComponent></ChildComponent>
</view>
</template>
<script setup lang="ts">
import { provide } from 'vue';
import ChildComponent from './ChildComponent.vue';
// 提供数据
provide('userInfo', {
name: '张三',
age: 25
});
</script>
<!-- 子组件 -->
<template>
<view class="child-container">
<text>{{ userInfo.name }}</text>
</view>
</template>
<script setup lang="ts">
import { inject } from 'vue';
// 注入数据
interface UserInfo {
name: string;
age: number;
}
const userInfo = inject<UserInfo>('userInfo', { name: '', age: 0 });
</script>五、Taro API调用
5.1 网络请求
import Taro from '@tarojs/taro';
// 发起GET请求
Taro.request({
url: 'https://api.example.com/users',
method: 'GET',
data: {
page: 1,
pageSize: 10
},
success: (res) => {
console.log('Request success:', res.data);
},
fail: (err) => {
console.error('Request failed:', err);
}
});
// 发起POST请求
Taro.request({
url: 'https://api.example.com/users',
method: 'POST',
data: {
name: '张三',
age: 25
},
header: {
'Content-Type': 'application/json'
},
success: (res) => {
console.log('Request success:', res.data);
}
});
// 上传文件
Taro.uploadFile({
url: 'https://api.example.com/upload',
filePath: tempFilePath,
name: 'file',
formData: {
'user': 'test'
},
success: (res) => {
console.log('Upload success:', res.data);
}
});
// 下载文件
Taro.downloadFile({
url: 'https://example.com/file.pdf',
success: (res) => {
if (res.statusCode === 200) {
console.log('Download success:', res.tempFilePath);
}
}
});5.2 本地存储
import Taro from '@tarojs/taro';
// 设置存储
Taro.setStorage({
key: 'userInfo',
data: {
name: '张三',
age: 25
},
success: () => {
console.log('Storage set successfully');
}
});
// 获取存储
Taro.getStorage({
key: 'userInfo',
success: (res) => {
console.log('Storage get successfully:', res.data);
}
});
// 移除存储
Taro.removeStorage({
key: 'userInfo',
success: () => {
console.log('Storage removed successfully');
}
});
// 清空存储
Taro.clearStorage({
success: () => {
console.log('Storage cleared successfully');
}
});5.3 界面交互
import Taro from '@tarojs/taro';
// 显示提示框
Taro.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
});
// 显示加载框
Taro.showLoading({
title: '加载中...'
});
// 隐藏加载框
Taro.hideLoading();
// 显示模态框
Taro.showModal({
title: '提示',
content: '确定要执行此操作吗?',
success: (res) => {
if (res.confirm) {
console.log('用户点击确定');
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
// 显示操作菜单
Taro.showActionSheet({
itemList: ['选项1', '选项2', '选项3'],
success: (res) => {
console.log('用户选择了', res.tapIndex);
}
});5.4 设备API
import Taro from '@tarojs/taro';
// 获取系统信息
Taro.getSystemInfo({
success: (res) => {
console.log('System info:', res);
// res.platform: 平台信息
// res.screenWidth: 屏幕宽度
// res.screenHeight: 屏幕高度
// res.model: 设备型号
// res.version: 操作系统版本
}
});
// 获取网络状态
Taro.getNetworkType({
success: (res) => {
console.log('Network type:', res.networkType);
}
});
// 监听网络状态变化
Taro.onNetworkStatusChange((res) => {
console.log('Network status changed:', res.isConnected, res.networkType);
});
// 获取位置信息
Taro.getLocation({
type: 'gcj02',
success: (res) => {
console.log('Location:', res.latitude, res.longitude);
}
});六、Taro状态管理
6.1 使用Pinia进行状态管理
6.1.1 安装Pinia
npm install pinia @tarojs/plugin-pinia6.1.2 配置Pinia插件
// config/index.js
module.exports = {
// ...
plugins: [
'@tarojs/plugin-pinia'
]
// ...
};6.1.3 创建Store
// src/store/user.ts
import { defineStore } from 'pinia';
interface UserInfo {
name: string;
age: number;
}
export const useUserStore = defineStore('user', {
state: () => ({
userInfo: null as UserInfo | null,
token: ''
}),
getters: {
isLoggedIn: (state) => !!state.token,
userName: (state) => state.userInfo?.name || ''
},
actions: {
login(userInfo: UserInfo, token: string) {
this.userInfo = userInfo;
this.token = token;
// 保存到本地存储
Taro.setStorageSync('userInfo', userInfo);
Taro.setStorageSync('token', token);
},
logout() {
this.userInfo = null;
this.token = '';
// 清除本地存储
Taro.removeStorageSync('userInfo');
Taro.removeStorageSync('token');
},
init() {
// 从本地存储恢复状态
this.userInfo = Taro.getStorageSync('userInfo');
this.token = Taro.getStorageSync('token');
}
}
});6.1.4 使用Store
<template>
<view class="container">
<text v-if="userStore.isLoggedIn">欢迎,{{ userStore.userName }}</text>
<button v-else @click="handleLogin">登录</button>
<button v-if="userStore.isLoggedIn" @click="handleLogout">退出</button>
</view>
</template>
<script setup lang="ts">
import { useUserStore } from '@/store/user';
import Taro from '@tarojs/taro';
const userStore = useUserStore();
// 初始化状态
userStore.init();
const handleLogin = () => {
userStore.login(
{ name: '张三', age: 25 },
'token123456'
);
};
const handleLogout = () => {
userStore.logout();
};
</script>七、Taro跨平台适配
7.1 条件编译
条件编译是Taro的核心特性,允许开发者根据不同平台编写不同的代码,实现平台特有的功能。
7.1.1 模板条件编译
<template>
<view class="container">
{/* #ifdef H5 */}
<text>H5平台特有内容</text>
{/* #endif */}
{/* #ifdef MP-WEIXIN */}
<text>微信小程序特有内容</text>
{/* #endif */}
{/* #ifdef APP */}
<text>React Native平台特有内容</text>
{/* #endif */}
{/* #ifdef MP-WEIXIN || MP-ALIPAY */}
<text>微信小程序和支付宝小程序共有内容</text>
{/* #endif */}
</view>
</template>7.1.2 脚本条件编译
<script setup lang="ts">
// #ifdef H5
console.log('H5平台');
// #endif
// #ifdef MP-WEIXIN
console.log('微信小程序');
// #endif
// #ifdef APP
console.log('React Native平台');
// #endif
</script>7.1.3 样式条件编译
/* #ifdef H5 */
.container {
background-color: #f0f0f0;
}
/* #endif */
/* #ifdef MP-WEIXIN */
.container {
background-color: #ffffff;
}
/* #endif */7.2 平台特有API调用
import Taro from '@tarojs/taro';
// 调用微信小程序特有API
// #ifdef MP-WEIXIN
Taro.requestSubscribeMessage({
tmplIds: ['template-id'],
success: (res) => {
console.log('Subscribe message success:', res);
}
});
// #endif
// 调用React Native特有API
// #ifdef APP
// 使用React Native API
// #endif7.3 适配不同屏幕尺寸
7.3.1 使用rpx单位
<template>
<view class="container" style="width: 750rpx; height: 200rpx;">
<text style="font-size: 32rpx;">文本内容</text>
</view>
</template>7.3.2 使用像素转换工具
// src/utils/pxTransform.ts
import Taro from '@tarojs/taro';
/**
* 将px转换为rpx
*/
export const px2rpx = (px: number): number => {
const systemInfo = Taro.getSystemInfoSync();
return px * (750 / systemInfo.windowWidth);
};
/**
* 将rpx转换为px
*/
export const rpx2px = (rpx: number): number => {
const systemInfo = Taro.getSystemInfoSync();
return rpx * (systemInfo.windowWidth / 750);
};八、Taro打包与发布
8.1 打包命令
# 打包微信小程序
npm run build:weapp
# 打包支付宝小程序
npm run build:alipay
# 打包百度小程序
npm run build:swan
# 打包头条小程序
npm run build:tt
# 打包QQ小程序
npm run build:qq
# 打包H5
npm run build:h5
# 打包React Native
npm run build:rn8.2 开发预览
# 预览微信小程序
npm run dev:weapp
# 预览H5
npm run dev:h5
# 预览React Native
npm run dev:rn8.3 持续集成与自动化部署
# .github/workflows/taro-ci.yml
name: Taro CI/CD
on:
push:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '16.x'
- name: Install dependencies
run: npm install
- name: Build WeChat Mini Program
run: npm run build:weapp
- name: Build H5
run: npm run build:h5
- name: Deploy H5 to GitHub Pages
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./dist
- name: Upload WeChat Mini Program
uses: easingthemes/ssh-deploy@v2.1.5
with:
SSH_PRIVATE_KEY: ${{ secrets.SSH_PRIVATE_KEY }}
REMOTE_HOST: ${{ secrets.REMOTE_HOST }}
REMOTE_USER: ${{ secrets.REMOTE_USER }}
TARGET: ${{ secrets.REMOTE_TARGET }}
SOURCE: ./dist/weapp九、Taro性能优化
9.1 代码优化
- 减少不必要的渲染:使用v-if和v-show合理控制元素渲染
- 使用虚拟列表:对于长列表,使用虚拟列表减少DOM节点数量
- 优化图片加载:使用图片懒加载,压缩图片大小
- 减少网络请求:合并请求,使用缓存
- 优化JavaScript代码:减少不必要的计算,使用防抖和节流
9.2 启动性能优化
- 减少启动包大小:移除不必要的依赖,使用按需引入
- 预加载关键资源:使用预加载API加载关键资源
- 延迟加载非关键资源:非关键资源延迟加载
- 优化页面结构:减少页面嵌套层级
9.3 运行时性能优化
- 使用原生组件:对于性能要求高的场景,使用原生组件
- 优化动画效果:使用CSS动画代替JavaScript动画
- 及时清理资源:页面卸载时清理定时器、事件监听等
- 优化列表渲染:使用key属性,避免不必要的重渲染
9.4 小程序性能优化
- 控制页面数量:避免过多页面同时存在
- 优化setData调用:减少setData调用次数,合并数据
- 使用自定义组件:提高组件复用性,减少代码量
- 优化wx:if和hidden:合理使用条件渲染
十、Taro最佳实践
10.1 项目结构最佳实践
- 模块化开发:按功能模块组织代码,提高代码复用性
- 组件化设计:将通用UI组件抽象出来,便于维护和复用
- 状态管理:使用Pinia或Redux进行状态管理,便于组件间通信
- API封装:封装网络请求和本地存储等API,便于统一管理
- 工具函数:将通用工具函数抽象出来,提高代码复用性
10.2 开发流程最佳实践
- 使用Git进行版本控制:便于团队协作和代码管理
- 编写单元测试:提高代码质量,减少bug
- 使用ESLint进行代码检查:保持代码风格一致
- 使用Prettier进行代码格式化:保持代码格式一致
- 定期进行代码审查:提高代码质量,分享知识
10.3 性能优化最佳实践
- 使用rpx单位:适配不同屏幕尺寸
- 合理使用条件编译:针对不同平台进行优化
- 优化图片资源:压缩图片大小,使用合适的格式
- 减少DOM节点数量:使用虚拟列表,合理嵌套组件
- 优化网络请求:合并请求,使用缓存,减少请求次数
10.4 跨平台开发最佳实践
- 优先使用Taro统一API:减少平台特有代码
- 合理使用条件编译:针对平台差异进行适配
- 测试多平台兼容性:确保在所有目标平台上正常运行
- 关注平台特性:了解各平台的特性和限制
- 性能优化针对性:针对不同平台进行针对性的性能优化
十一、Taro 3.x新特性
11.1 Vue 3支持
Taro 3.x全面支持Vue 3,包括Composition API、Teleport、Suspense等特性。
11.2 编译优化
- 并行编译:提高编译速度
- 增量编译:只编译修改的文件
- Tree Shaking:移除未使用的代码
- 代码压缩:优化代码体积
11.3 运行时优化
- 小程序运行时优化:提高小程序运行性能
- H5运行时优化:优化H5渲染性能
- React Native运行时优化:提高React Native应用性能
11.4 开发体验优化
- 热更新:支持快速热更新
- 调试工具:提供丰富的调试工具
- 错误提示:优化错误提示信息
- 文档完善:提供详细的文档和示例
十二、总结与展望
12.1 核心技术总结
- Taro框架:开放式跨端跨框架解决方案,支持React、Vue等多种框架
- 项目结构:清晰的目录结构,便于维护和扩展
- 页面开发:丰富的生命周期和路由管理
- 组件开发:支持自定义组件和全局组件注册
- API调用:统一的API调用方式,屏蔽平台差异
- 状态管理:支持Pinia和Redux进行状态管理
- 跨平台适配:条件编译和平台特有API调用
- 打包发布:支持多平台打包和发布
- 性能优化:多种性能优化策略
12.2 未来发展趋势
- 更好的Vue 3支持:持续优化Vue 3的支持
- 更多平台支持:支持更多的小程序平台和新的跨平台技术
- 更好的性能:持续优化各平台的性能
- 更丰富的生态:提供更多高质量的组件库和插件
- 更好的开发体验:优化开发工具和调试体验
12.3 学习建议
- 官方文档:深入学习Taro官方文档,掌握核心概念和API
- 实战项目:通过实战项目掌握Taro开发技巧
- 社区资源:关注Taro社区,学习他人的经验和技巧
- 平台特性:了解各平台的特性和限制,便于进行跨平台适配
- 性能优化:学习性能优化技巧,提高应用的用户体验
通过本集的学习,相信你已经掌握了Taro多端开发的核心概念和深度使用技巧,能够使用Taro开发高质量的跨平台应用。在实际开发中,应根据项目需求和目标平台,选择合适的技术方案和优化策略,不断提升应用的质量和用户体验。