hookehuyr

docs: 添加 Taro 开发速查表文档

新增 Taro 快速参考指南,汇总禁止使用的 Web API、推荐使用的 Taro API、生命周期对比、样式规范、常见陷阱和性能优化建议。帮助开发者避免使用不兼容的 Web API 并遵循最佳实践,提升开发效率和代码质量。
# Taro 开发速查表
> **用途**: 快速查阅 Taro API 和常见陷阱,避免使用错误的 Web API
## 🚫 禁止使用的 Web API
### ❌ DOM 操作
```javascript
// ❌ 这些在小程序中不存在
window.document
document.getElementById
document.querySelector
document.querySelectorAll
```
### ❌ 浏览器存储
```javascript
// ❌ 使用 Taro 替代
localStorage.setItem()
localStorage.getItem()
sessionStorage.setItem()
sessionStorage.getItem()
```
### ❌ 浏览器路由
```javascript
// ❌ 使用 Taro 替代
window.location.href
window.location.pathname
history.pushState()
history.replaceState()
```
### ❌ 网络请求
```javascript
// ❌ 使用 Taro 替代
fetch()
XMLHttpRequest()
```
### ❌ 其他 Web API
```javascript
// ❌ 使用 Taro 替代
window.scrollTo()
window.addEventListener()
requestAnimationFrame()
```
---
## ✅ 必须使用的 Taro API
### 🔄 路由导航
```javascript
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**:
```javascript
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()
```
### 💾 本地存储
```javascript
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() // 清空所有
```
### 🌐 网络请求
```javascript
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` 封装**:
```javascript
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)
}
})
```
### 💬 提示反馈
```javascript
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('用户点击确定')
}
}
})
```
### 🎯 设备能力
```javascript
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 查询)
```javascript
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` | 页面隐藏时触发 | ⭐⭐ |
**示例**:
```vue
<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 生命周期:
```vue
<script setup>
import { onMounted, onUnmounted } from 'vue'
onMounted(() => {
// ✅ 组件挂载
})
onUnmounted(() => {
// ✅ 组件卸载
})
</script>
```
---
## 🎨 样式规范
### 单位使用
```vue
<!-- ✅ 使用 px(Taro 自动转换为 rpx) -->
<view style="width: 100px; height: 200px;">
内容
</view>
<!-- ❌ 避免直接写 rpx -->
<view style="width: 100rpx;">
内容
</view>
```
### 双设计宽度系统
**项目配置**:
- **NutUI 组件**: 参考 375px 设计稿
- **自定义页面**: 参考 750px 设计稿
```vue
<!-- NutUI 组件: 375px 基准 -->
<nut-button :custom-style="{ fontSize: '14px' }">
按钮
</nut-button>
<!-- 自定义元素: 750px 基准 -->
<view class="custom-box" style="width: 750px;">
内容
</view>
```
### 伪元素限制
```vue
<!-- ❌ 小程序不支持大部分伪元素 -->
<style>
.custom-element::before {
content: ''; /* 不会生效 */
}
</style>
<!-- ✅ 使用嵌套元素代替 -->
<template>
<view class="custom-element">
<view class="custom-element-icon"></view>
<text>内容</text>
</view>
</template>
```
---
## 🔐 SessionID 管理(核心)
### ✅ 正确实现
```javascript
// 动态获取 sessionid 并设置到请求头
const sessionid = getSessionId() // 从 localStorage.sessionid 读取
if (sessionid) {
config.headers.cookie = sessionid // 设置到 cookie 字段
}
```
### ❌ 错误实现
```javascript
// ❌ 静态 sessionid
const sessionid = 'static_value'
// ❌ 字段名错误
config.headers.sessionid = sessionid
// ❌ 不设置到请求头
// 只读取了 sessionid,但没有设置到 headers
```
---
## ⚠️ 常见陷阱
### 1. NutUI textarea 样式无法覆盖
**问题**:
```vue
<!-- ❌ 深度选择器无效 -->
<nut-textarea v-model="content" class="custom-textarea" />
```
**解决**:
```vue
<!-- ✅ 使用原生 textarea -->
<textarea
v-model="content"
class="custom-textarea"
maxlength="200"
/>
```
### 2. IconFont 动态切换不响应
**问题**:
```vue
<!-- ❌ 图标不更新 -->
<IconFont :name="iconName" />
```
**解决**:
```vue
<!-- ✅ 添加 key 强制重新渲染 -->
<IconFont :name="iconName" :key="iconName" />
```
### 3. 嵌套弹窗层级冲突
**问题**:
```vue
<!-- ❌ 真机上内层弹窗被外层弹窗遮挡 -->
<PlanPopup>
<nut-popup v-model:visible="showPicker">
<nut-picker />
</nut-popup>
</PlanPopup>
```
**解决**:
```vue
<!-- ✅ 将内层弹窗提升到外层 -->
<PlanPopup />
<nut-popup
v-model:visible="showPicker"
:z-index="9999"
:overlay="true"
>
<nut-picker />
</nut-popup>
```
### 4. SVG 图标加载失败
**问题**:
```javascript
// ❌ 字符串路径导致 500 错误
const icons = {
pdf: '/assets/images/icon/doc/pdf.svg'
}
```
**解决**:
```javascript
// ✅ 使用 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` | 统一的列表点击处理 |
**抽取示例**:
```javascript
// ✅ GOOD - 创建 useFileOperation Composable
export function useFileOperation() {
const viewFile = async (file) => {
// 文件预览逻辑
}
const downloadFile = async (file) => {
// 文件下载逻辑
}
return { viewFile, downloadFile }
}
// 使用
const { viewFile } = useFileOperation()
```
---
## 🔧 性能优化
### 响应式数据优化
```javascript
import { shallowRef, markRaw } from 'vue'
// ❌ BAD - 深度响应式
const menuItems = ref([
{ icon: IconFont, name: 'heart' } // Vue 会深度代理组件对象
])
// ✅ GOOD - 浅层响应式
const menuItems = shallowRef([
{ icon: markRaw(IconFont), name: 'heart' } // 避免深度代理
])
```
### 图片优化
```javascript
// 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 注释(必须)
```javascript
/**
* 获取产品列表
*
* @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) {
// ...
}
```
### 错误处理
```javascript
// ✅ GOOD - 统一错误处理
try {
await someAPI()
} catch (err) {
console.error('操作失败:', err)
Taro.showToast({
title: '操作失败,请重试',
icon: 'none'
})
}
```
---
## 🔍 调试技巧
### 检查网络请求
```javascript
// 检查网络类型
const { networkType } = await Taro.getNetworkType()
if (networkType === 'none') {
console.error('网络未连接')
}
```
### 性能监控
```javascript
import { useReady } from '@tarojs/taro'
const startTime = Date.now()
useReady(() => {
const loadTime = Date.now() - startTime
console.log('页面加载耗时:', loadTime)
})
```
---
## 📚 参考资源
- [Taro 官方文档](https://docs.taro.zone/)
- [微信小程序开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/)
- [项目经验教训总结](./lessons-learned.md)
- [Taro 开发规范](~/.claude/rules/taro-patterns.md)
- [小程序开发检查清单](~/.claude/rules/miniprogram-checklist.md)
---
**最后更新**: 2026-02-03
**维护者**: Claude Code
**项目**: Manulife WeApp