TARO_QUICK_REFERENCE.md
11.2 KB
Taro 开发速查表
用途: 快速查阅 Taro API 和常见陷阱,避免使用错误的 Web API
🚫 禁止使用的 Web API
❌ DOM 操作
// ❌ 这些在小程序中不存在
window.document
document.getElementById
document.querySelector
document.querySelectorAll
❌ 浏览器存储
// ❌ 使用 Taro 替代
localStorage.setItem()
localStorage.getItem()
sessionStorage.setItem()
sessionStorage.getItem()
❌ 浏览器路由
// ❌ 使用 Taro 替代
window.location.href
window.location.pathname
history.pushState()
history.replaceState()
❌ 网络请求
// ❌ 使用 Taro 替代
fetch()
XMLHttpRequest()
❌ 其他 Web API
// ❌ 使用 Taro 替代
window.scrollTo()
window.addEventListener()
requestAnimationFrame()
✅ 必须使用的 Taro API
🔄 路由导航
import Taro from '@tarojs/taro'
// 跳转到新页面(保留当前页)
Taro.navigateTo({
url: '/pages/detail/index?id=123'
})
// 关闭当前页,跳转到新页面
Taro.redirectTo({
url: '/pages/login/index'
})
// 跳转到 tabbar 页面
Taro.switchTab({
url: '/pages/index/index'
})
// 返回上一页
Taro.navigateBack({
delta: 1
})
// 获取页面参数(在页面中)
import { useLoad } from '@tarojs/taro'
useLoad((options) => {
console.log('页面参数:', options.id)
})
推荐使用项目的 useGo hook:
import { useGo } from '@/hooks/useGo'
const go = useGo()
// 短路径跳转(自动补全)
go('product-detail', { id: 123 }) // → pages/product-detail/index?id=123
go('material-list') // → pages/material-list/index
// 返回
go.back()
💾 本地存储
import Taro from '@tarojs/taro'
// 异步存储
await Taro.setStorage({ key: 'user', data: userInfo })
const { data } = await Taro.getStorage({ key: 'user' })
// 同步存储(谨慎使用,会阻塞线程)
Taro.setStorageSync('token', 'xxxx')
const token = Taro.getStorageSync('token')
// 删除存储
Taro.removeStorage({ key: 'user' })
Taro.clearStorage() // 清空所有
🌐 网络请求
import Taro from '@tarojs/taro'
// 基础请求
Taro.request({
url: 'https://api.example.com/data',
method: 'GET',
data: { id: 123 },
header: {
'content-type': 'application/json'
}
}).then(res => {
console.log(res.data)
})
推荐使用项目的 request.js 封装:
import request from '@/utils/request'
request({
url: '/api/data',
method: 'GET',
params: { id: 123 }
}).then(res => {
if (res.data.code === 1) {
console.log('成功:', res.data.data)
}
})
💬 提示反馈
import Taro from '@tarojs/taro'
// Toast 提示
Taro.showToast({
title: '操作成功',
icon: 'success',
duration: 2000
})
// Loading 提示
Taro.showLoading({
title: '加载中...',
mask: true // 防止触摸穿透
})
// 操作完成后
Taro.hideLoading()
// Modal 弹窗
Taro.showModal({
title: '提示',
content: '确定删除吗?',
success: (res) => {
if (res.confirm) {
console.log('用户点击确定')
}
}
})
🎯 设备能力
import Taro from '@tarojs/taro'
// 获取系统信息
const { system, statusBarHeight } = await Taro.getSystemInfo()
// 获取定位
const { latitude, longitude } = await Taro.getLocation({
type: 'wgs84'
})
// 选择图片
const { tempFilePaths } = await Taro.chooseImage({
count: 1,
sizeType: ['compressed']
})
// 预览图片
Taro.previewImage({
current: 'current.jpg',
urls: ['1.jpg', '2.jpg']
})
// 复制到剪贴板
await Taro.setClipboardData({
data: '复制的文本'
})
🔍 选择器(替代 DOM 查询)
import Taro from '@tarojs/taro'
// 查询节点信息
const query = Taro.createSelectorQuery()
query.select('#myElement').boundingClientRect()
query.exec((res) => {
if (res[0]) {
console.log('元素位置:', res[0])
console.log('宽度:', res[0].width)
console.log('高度:', res[0].height)
}
})
🔄 生命周期对比
页面生命周期
| Vue 3 | Taro (页面) | 说明 | 推荐度 |
|---|---|---|---|
❌ onMounted
|
✅ useLoad
|
页面加载时触发一次 | ⭐⭐⭐ |
❌ onActivated
|
✅ useShow
|
每次显示时触发 | ⭐⭐⭐ |
❌ onMounted
|
✅ useReady
|
首次渲染完成 | ⭐⭐ |
❌ onDeactivated
|
✅ useHide
|
页面隐藏时触发 | ⭐⭐ |
示例:
<script setup>
import { useLoad, useShow, useReady } from '@tarojs/taro'
import { ref } from 'vue'
const list = ref([])
// ✅ 页面加载时触发一次
useLoad((options) => {
console.log('页面参数:', options)
// 获取页面参数、初始化数据
fetchInitialData(options.id)
})
// ✅ 每次显示时触发
useShow(() => {
console.log('页面显示')
// 刷新数据、重新获取定位
refreshList()
})
// ✅ 首次渲染完成
useReady(() => {
console.log('页面渲染完成')
// 操作 DOM、初始化组件
initChart()
})
// ❌ 避免在页面中使用 Vue 生命周期
// import { onMounted } from 'vue'
// onMounted(() => { ... }) // 可能不按预期工作
</script>
组件生命周期
组件可以使用 Vue 3 生命周期:
<script setup>
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
// ✅ 组件挂载
})
onUnmounted(() => {
// ✅ 组件卸载
})
</script>
🎨 样式规范
单位使用
<!-- ✅ 使用 px(Taro 自动转换为 rpx) -->
<view style="width: 100px; height: 200px;">
内容
</view>
<!-- ❌ 避免直接写 rpx -->
<view style="width: 100rpx;">
内容
</view>
双设计宽度系统
项目配置:
- NutUI 组件: 参考 375px 设计稿
- 自定义页面: 参考 750px 设计稿
<!-- NutUI 组件: 375px 基准 -->
<nut-button :custom-style="{ fontSize: '14px' }">
按钮
</nut-button>
<!-- 自定义元素: 750px 基准 -->
<view class="custom-box" style="width: 750px;">
内容
</view>
伪元素限制
<!-- ❌ 小程序不支持大部分伪元素 -->
<style>
.custom-element::before {
content: ''; /* 不会生效 */
}
</style>
<!-- ✅ 使用嵌套元素代替 -->
<template>
<view class="custom-element">
<view class="custom-element-icon"></view>
<text>内容</text>
</view>
</template>
🔐 SessionID 管理(核心)
✅ 正确实现
// 动态获取 sessionid 并设置到请求头
const sessionid = getSessionId() // 从 localStorage.sessionid 读取
if (sessionid) {
config.headers.cookie = sessionid // 设置到 cookie 字段
}
❌ 错误实现
// ❌ 静态 sessionid
const sessionid = 'static_value'
// ❌ 字段名错误
config.headers.sessionid = sessionid
// ❌ 不设置到请求头
// 只读取了 sessionid,但没有设置到 headers
⚠️ 常见陷阱
1. NutUI textarea 样式无法覆盖
问题:
<!-- ❌ 深度选择器无效 -->
<nut-textarea v-model="content" class="custom-textarea" />
解决:
<!-- ✅ 使用原生 textarea -->
<textarea
v-model="content"
class="custom-textarea"
maxlength="200"
/>
2. IconFont 动态切换不响应
问题:
<!-- ❌ 图标不更新 -->
<IconFont :name="iconName" />
解决:
<!-- ✅ 添加 key 强制重新渲染 -->
<IconFont :name="iconName" :key="iconName" />
3. 嵌套弹窗层级冲突
问题:
<!-- ❌ 真机上内层弹窗被外层弹窗遮挡 -->
<PlanPopup>
<nut-popup v-model:visible="showPicker">
<nut-picker />
</nut-popup>
</PlanPopup>
解决:
<!-- ✅ 将内层弹窗提升到外层 -->
<PlanPopup />
<nut-popup
v-model:visible="showPicker"
:z-index="9999"
:overlay="true"
>
<nut-picker />
</nut-popup>
4. SVG 图标加载失败
问题:
// ❌ 字符串路径导致 500 错误
const icons = {
pdf: '/assets/images/icon/doc/pdf.svg'
}
解决:
// ✅ 使用 import 导入
import pdfIcon from '@/assets/images/icon/doc/pdf.svg'
const icons = { pdf: pdfIcon }
📦 Composable 抽取原则
"第 3 次出现原则": 代码重复 3 次时必须抽取
判断标准
| 场景 | 抽取条件 | 抽取目标 |
|---|---|---|
| 代码重复 | ≥ 2 次 | 警惕 |
| 代码重复 | ≥ 3 次 | 必须抽取 |
| v-for 模板 | > 5 行 | 提取列表项组件 |
| 组件模板 | > 150 行 | 拆分组件 |
| 函数长度 | > 50 行 | 拆分函数 |
项目中的 Composables
| Composable | 用途 |
|---|---|
useSectionList |
分组列表管理 |
useFileOperation |
文件下载、预览、打开 |
useListItemClick |
统一的列表点击处理 |
抽取示例:
// ✅ GOOD - 创建 useFileOperation Composable
export function useFileOperation() {
const viewFile = async (file) => {
// 文件预览逻辑
}
const downloadFile = async (file) => {
// 文件下载逻辑
}
return { viewFile, downloadFile }
}
// 使用
const { viewFile } = useFileOperation()
🔧 性能优化
响应式数据优化
import { shallowRef, markRaw } from 'vue'
// ❌ BAD - 深度响应式
const menuItems = ref([
{ icon: IconFont, name: 'heart' } // Vue 会深度代理组件对象
])
// ✅ GOOD - 浅层响应式
const menuItems = shallowRef([
{ icon: markRaw(IconFont), name: 'heart' } // 避免深度代理
])
图片优化
// CDN 图片优化
function optimizeImageUrl(url, width = 750, quality = 70) {
if (!url || !url.includes('cdn.ipadbiz.cn')) {
return url
}
return `${url}?imageMogr2/thumbnail/${width}x/quality/${quality}`
}
📝 代码规范
JSDoc 注释(必须)
/**
* 获取产品列表
*
* @description 从 API 获取产品列表数据
* @param {Object} params - 查询参数
* @param {number} params.page - 页码
* @param {number} params.limit - 每页数量
* @returns {Promise<Object>} 产品列表数据
*
* @example
* const list = await getProductList({ page: 1, limit: 10 })
*/
export async function getProductList(params) {
// ...
}
错误处理
// ✅ GOOD - 统一错误处理
try {
await someAPI()
} catch (err) {
console.error('操作失败:', err)
Taro.showToast({
title: '操作失败,请重试',
icon: 'none'
})
}
🔍 调试技巧
检查网络请求
// 检查网络类型
const { networkType } = await Taro.getNetworkType()
if (networkType === 'none') {
console.error('网络未连接')
}
性能监控
import { useReady } from '@tarojs/taro'
const startTime = Date.now()
useReady(() => {
const loadTime = Date.now() - startTime
console.log('页面加载耗时:', loadTime)
})
📚 参考资源
最后更新: 2026-02-03 维护者: Claude Code 项目: Manulife WeApp