Taro 开发速查表.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