docs: 添加 Taro 开发速查表文档
新增 Taro 快速参考指南,汇总禁止使用的 Web API、推荐使用的 Taro API、生命周期对比、样式规范、常见陷阱和性能优化建议。帮助开发者避免使用不兼容的 Web API 并遵循最佳实践,提升开发效率和代码质量。
Showing
1 changed file
with
600 additions
and
0 deletions
docs/TARO_QUICK_REFERENCE.md
0 → 100644
| 1 | +# Taro 开发速查表 | ||
| 2 | + | ||
| 3 | +> **用途**: 快速查阅 Taro API 和常见陷阱,避免使用错误的 Web API | ||
| 4 | + | ||
| 5 | +## 🚫 禁止使用的 Web API | ||
| 6 | + | ||
| 7 | +### ❌ DOM 操作 | ||
| 8 | +```javascript | ||
| 9 | +// ❌ 这些在小程序中不存在 | ||
| 10 | +window.document | ||
| 11 | +document.getElementById | ||
| 12 | +document.querySelector | ||
| 13 | +document.querySelectorAll | ||
| 14 | +``` | ||
| 15 | + | ||
| 16 | +### ❌ 浏览器存储 | ||
| 17 | +```javascript | ||
| 18 | +// ❌ 使用 Taro 替代 | ||
| 19 | +localStorage.setItem() | ||
| 20 | +localStorage.getItem() | ||
| 21 | +sessionStorage.setItem() | ||
| 22 | +sessionStorage.getItem() | ||
| 23 | +``` | ||
| 24 | + | ||
| 25 | +### ❌ 浏览器路由 | ||
| 26 | +```javascript | ||
| 27 | +// ❌ 使用 Taro 替代 | ||
| 28 | +window.location.href | ||
| 29 | +window.location.pathname | ||
| 30 | +history.pushState() | ||
| 31 | +history.replaceState() | ||
| 32 | +``` | ||
| 33 | + | ||
| 34 | +### ❌ 网络请求 | ||
| 35 | +```javascript | ||
| 36 | +// ❌ 使用 Taro 替代 | ||
| 37 | +fetch() | ||
| 38 | +XMLHttpRequest() | ||
| 39 | +``` | ||
| 40 | + | ||
| 41 | +### ❌ 其他 Web API | ||
| 42 | +```javascript | ||
| 43 | +// ❌ 使用 Taro 替代 | ||
| 44 | +window.scrollTo() | ||
| 45 | +window.addEventListener() | ||
| 46 | +requestAnimationFrame() | ||
| 47 | +``` | ||
| 48 | + | ||
| 49 | +--- | ||
| 50 | + | ||
| 51 | +## ✅ 必须使用的 Taro API | ||
| 52 | + | ||
| 53 | +### 🔄 路由导航 | ||
| 54 | +```javascript | ||
| 55 | +import Taro from '@tarojs/taro' | ||
| 56 | + | ||
| 57 | +// 跳转到新页面(保留当前页) | ||
| 58 | +Taro.navigateTo({ | ||
| 59 | + url: '/pages/detail/index?id=123' | ||
| 60 | +}) | ||
| 61 | + | ||
| 62 | +// 关闭当前页,跳转到新页面 | ||
| 63 | +Taro.redirectTo({ | ||
| 64 | + url: '/pages/login/index' | ||
| 65 | +}) | ||
| 66 | + | ||
| 67 | +// 跳转到 tabbar 页面 | ||
| 68 | +Taro.switchTab({ | ||
| 69 | + url: '/pages/index/index' | ||
| 70 | +}) | ||
| 71 | + | ||
| 72 | +// 返回上一页 | ||
| 73 | +Taro.navigateBack({ | ||
| 74 | + delta: 1 | ||
| 75 | +}) | ||
| 76 | + | ||
| 77 | +// 获取页面参数(在页面中) | ||
| 78 | +import { useLoad } from '@tarojs/taro' | ||
| 79 | +useLoad((options) => { | ||
| 80 | + console.log('页面参数:', options.id) | ||
| 81 | +}) | ||
| 82 | +``` | ||
| 83 | + | ||
| 84 | +**推荐使用项目的 `useGo` hook**: | ||
| 85 | +```javascript | ||
| 86 | +import { useGo } from '@/hooks/useGo' | ||
| 87 | +const go = useGo() | ||
| 88 | + | ||
| 89 | +// 短路径跳转(自动补全) | ||
| 90 | +go('product-detail', { id: 123 }) // → pages/product-detail/index?id=123 | ||
| 91 | +go('material-list') // → pages/material-list/index | ||
| 92 | + | ||
| 93 | +// 返回 | ||
| 94 | +go.back() | ||
| 95 | +``` | ||
| 96 | + | ||
| 97 | +### 💾 本地存储 | ||
| 98 | +```javascript | ||
| 99 | +import Taro from '@tarojs/taro' | ||
| 100 | + | ||
| 101 | +// 异步存储 | ||
| 102 | +await Taro.setStorage({ key: 'user', data: userInfo }) | ||
| 103 | +const { data } = await Taro.getStorage({ key: 'user' }) | ||
| 104 | + | ||
| 105 | +// 同步存储(谨慎使用,会阻塞线程) | ||
| 106 | +Taro.setStorageSync('token', 'xxxx') | ||
| 107 | +const token = Taro.getStorageSync('token') | ||
| 108 | + | ||
| 109 | +// 删除存储 | ||
| 110 | +Taro.removeStorage({ key: 'user' }) | ||
| 111 | +Taro.clearStorage() // 清空所有 | ||
| 112 | +``` | ||
| 113 | + | ||
| 114 | +### 🌐 网络请求 | ||
| 115 | +```javascript | ||
| 116 | +import Taro from '@tarojs/taro' | ||
| 117 | + | ||
| 118 | +// 基础请求 | ||
| 119 | +Taro.request({ | ||
| 120 | + url: 'https://api.example.com/data', | ||
| 121 | + method: 'GET', | ||
| 122 | + data: { id: 123 }, | ||
| 123 | + header: { | ||
| 124 | + 'content-type': 'application/json' | ||
| 125 | + } | ||
| 126 | +}).then(res => { | ||
| 127 | + console.log(res.data) | ||
| 128 | +}) | ||
| 129 | +``` | ||
| 130 | + | ||
| 131 | +**推荐使用项目的 `request.js` 封装**: | ||
| 132 | +```javascript | ||
| 133 | +import request from '@/utils/request' | ||
| 134 | + | ||
| 135 | +request({ | ||
| 136 | + url: '/api/data', | ||
| 137 | + method: 'GET', | ||
| 138 | + params: { id: 123 } | ||
| 139 | +}).then(res => { | ||
| 140 | + if (res.data.code === 1) { | ||
| 141 | + console.log('成功:', res.data.data) | ||
| 142 | + } | ||
| 143 | +}) | ||
| 144 | +``` | ||
| 145 | + | ||
| 146 | +### 💬 提示反馈 | ||
| 147 | +```javascript | ||
| 148 | +import Taro from '@tarojs/taro' | ||
| 149 | + | ||
| 150 | +// Toast 提示 | ||
| 151 | +Taro.showToast({ | ||
| 152 | + title: '操作成功', | ||
| 153 | + icon: 'success', | ||
| 154 | + duration: 2000 | ||
| 155 | +}) | ||
| 156 | + | ||
| 157 | +// Loading 提示 | ||
| 158 | +Taro.showLoading({ | ||
| 159 | + title: '加载中...', | ||
| 160 | + mask: true // 防止触摸穿透 | ||
| 161 | +}) | ||
| 162 | +// 操作完成后 | ||
| 163 | +Taro.hideLoading() | ||
| 164 | + | ||
| 165 | +// Modal 弹窗 | ||
| 166 | +Taro.showModal({ | ||
| 167 | + title: '提示', | ||
| 168 | + content: '确定删除吗?', | ||
| 169 | + success: (res) => { | ||
| 170 | + if (res.confirm) { | ||
| 171 | + console.log('用户点击确定') | ||
| 172 | + } | ||
| 173 | + } | ||
| 174 | +}) | ||
| 175 | +``` | ||
| 176 | + | ||
| 177 | +### 🎯 设备能力 | ||
| 178 | +```javascript | ||
| 179 | +import Taro from '@tarojs/taro' | ||
| 180 | + | ||
| 181 | +// 获取系统信息 | ||
| 182 | +const { system, statusBarHeight } = await Taro.getSystemInfo() | ||
| 183 | + | ||
| 184 | +// 获取定位 | ||
| 185 | +const { latitude, longitude } = await Taro.getLocation({ | ||
| 186 | + type: 'wgs84' | ||
| 187 | +}) | ||
| 188 | + | ||
| 189 | +// 选择图片 | ||
| 190 | +const { tempFilePaths } = await Taro.chooseImage({ | ||
| 191 | + count: 1, | ||
| 192 | + sizeType: ['compressed'] | ||
| 193 | +}) | ||
| 194 | + | ||
| 195 | +// 预览图片 | ||
| 196 | +Taro.previewImage({ | ||
| 197 | + current: 'current.jpg', | ||
| 198 | + urls: ['1.jpg', '2.jpg'] | ||
| 199 | +}) | ||
| 200 | + | ||
| 201 | +// 复制到剪贴板 | ||
| 202 | +await Taro.setClipboardData({ | ||
| 203 | + data: '复制的文本' | ||
| 204 | +}) | ||
| 205 | +``` | ||
| 206 | + | ||
| 207 | +### 🔍 选择器(替代 DOM 查询) | ||
| 208 | +```javascript | ||
| 209 | +import Taro from '@tarojs/taro' | ||
| 210 | + | ||
| 211 | +// 查询节点信息 | ||
| 212 | +const query = Taro.createSelectorQuery() | ||
| 213 | +query.select('#myElement').boundingClientRect() | ||
| 214 | +query.exec((res) => { | ||
| 215 | + if (res[0]) { | ||
| 216 | + console.log('元素位置:', res[0]) | ||
| 217 | + console.log('宽度:', res[0].width) | ||
| 218 | + console.log('高度:', res[0].height) | ||
| 219 | + } | ||
| 220 | +}) | ||
| 221 | +``` | ||
| 222 | + | ||
| 223 | +--- | ||
| 224 | + | ||
| 225 | +## 🔄 生命周期对比 | ||
| 226 | + | ||
| 227 | +### 页面生命周期 | ||
| 228 | + | ||
| 229 | +| Vue 3 | Taro (页面) | 说明 | 推荐度 | | ||
| 230 | +|-------|-----------|------|--------| | ||
| 231 | +| ❌ `onMounted` | ✅ `useLoad` | 页面加载时触发一次 | ⭐⭐⭐ | | ||
| 232 | +| ❌ `onActivated` | ✅ `useShow` | 每次显示时触发 | ⭐⭐⭐ | | ||
| 233 | +| ❌ `onMounted` | ✅ `useReady` | 首次渲染完成 | ⭐⭐ | | ||
| 234 | +| ❌ `onDeactivated` | ✅ `useHide` | 页面隐藏时触发 | ⭐⭐ | | ||
| 235 | + | ||
| 236 | +**示例**: | ||
| 237 | +```vue | ||
| 238 | +<script setup> | ||
| 239 | +import { useLoad, useShow, useReady } from '@tarojs/taro' | ||
| 240 | +import { ref } from 'vue' | ||
| 241 | + | ||
| 242 | +const list = ref([]) | ||
| 243 | + | ||
| 244 | +// ✅ 页面加载时触发一次 | ||
| 245 | +useLoad((options) => { | ||
| 246 | + console.log('页面参数:', options) | ||
| 247 | + // 获取页面参数、初始化数据 | ||
| 248 | + fetchInitialData(options.id) | ||
| 249 | +}) | ||
| 250 | + | ||
| 251 | +// ✅ 每次显示时触发 | ||
| 252 | +useShow(() => { | ||
| 253 | + console.log('页面显示') | ||
| 254 | + // 刷新数据、重新获取定位 | ||
| 255 | + refreshList() | ||
| 256 | +}) | ||
| 257 | + | ||
| 258 | +// ✅ 首次渲染完成 | ||
| 259 | +useReady(() => { | ||
| 260 | + console.log('页面渲染完成') | ||
| 261 | + // 操作 DOM、初始化组件 | ||
| 262 | + initChart() | ||
| 263 | +}) | ||
| 264 | + | ||
| 265 | +// ❌ 避免在页面中使用 Vue 生命周期 | ||
| 266 | +// import { onMounted } from 'vue' | ||
| 267 | +// onMounted(() => { ... }) // 可能不按预期工作 | ||
| 268 | +</script> | ||
| 269 | +``` | ||
| 270 | + | ||
| 271 | +### 组件生命周期 | ||
| 272 | + | ||
| 273 | +组件可以使用 Vue 3 生命周期: | ||
| 274 | +```vue | ||
| 275 | +<script setup> | ||
| 276 | +import { onMounted, onUnmounted } from 'vue' | ||
| 277 | + | ||
| 278 | +onMounted(() => { | ||
| 279 | + // ✅ 组件挂载 | ||
| 280 | +}) | ||
| 281 | + | ||
| 282 | +onUnmounted(() => { | ||
| 283 | + // ✅ 组件卸载 | ||
| 284 | +}) | ||
| 285 | +</script> | ||
| 286 | +``` | ||
| 287 | + | ||
| 288 | +--- | ||
| 289 | + | ||
| 290 | +## 🎨 样式规范 | ||
| 291 | + | ||
| 292 | +### 单位使用 | ||
| 293 | +```vue | ||
| 294 | +<!-- ✅ 使用 px(Taro 自动转换为 rpx) --> | ||
| 295 | +<view style="width: 100px; height: 200px;"> | ||
| 296 | + 内容 | ||
| 297 | +</view> | ||
| 298 | + | ||
| 299 | +<!-- ❌ 避免直接写 rpx --> | ||
| 300 | +<view style="width: 100rpx;"> | ||
| 301 | + 内容 | ||
| 302 | +</view> | ||
| 303 | +``` | ||
| 304 | + | ||
| 305 | +### 双设计宽度系统 | ||
| 306 | + | ||
| 307 | +**项目配置**: | ||
| 308 | +- **NutUI 组件**: 参考 375px 设计稿 | ||
| 309 | +- **自定义页面**: 参考 750px 设计稿 | ||
| 310 | + | ||
| 311 | +```vue | ||
| 312 | +<!-- NutUI 组件: 375px 基准 --> | ||
| 313 | +<nut-button :custom-style="{ fontSize: '14px' }"> | ||
| 314 | + 按钮 | ||
| 315 | +</nut-button> | ||
| 316 | + | ||
| 317 | +<!-- 自定义元素: 750px 基准 --> | ||
| 318 | +<view class="custom-box" style="width: 750px;"> | ||
| 319 | + 内容 | ||
| 320 | +</view> | ||
| 321 | +``` | ||
| 322 | + | ||
| 323 | +### 伪元素限制 | ||
| 324 | + | ||
| 325 | +```vue | ||
| 326 | +<!-- ❌ 小程序不支持大部分伪元素 --> | ||
| 327 | +<style> | ||
| 328 | +.custom-element::before { | ||
| 329 | + content: ''; /* 不会生效 */ | ||
| 330 | +} | ||
| 331 | +</style> | ||
| 332 | + | ||
| 333 | +<!-- ✅ 使用嵌套元素代替 --> | ||
| 334 | +<template> | ||
| 335 | + <view class="custom-element"> | ||
| 336 | + <view class="custom-element-icon"></view> | ||
| 337 | + <text>内容</text> | ||
| 338 | + </view> | ||
| 339 | +</template> | ||
| 340 | +``` | ||
| 341 | + | ||
| 342 | +--- | ||
| 343 | + | ||
| 344 | +## 🔐 SessionID 管理(核心) | ||
| 345 | + | ||
| 346 | +### ✅ 正确实现 | ||
| 347 | + | ||
| 348 | +```javascript | ||
| 349 | +// 动态获取 sessionid 并设置到请求头 | ||
| 350 | +const sessionid = getSessionId() // 从 localStorage.sessionid 读取 | ||
| 351 | +if (sessionid) { | ||
| 352 | + config.headers.cookie = sessionid // 设置到 cookie 字段 | ||
| 353 | +} | ||
| 354 | +``` | ||
| 355 | + | ||
| 356 | +### ❌ 错误实现 | ||
| 357 | + | ||
| 358 | +```javascript | ||
| 359 | +// ❌ 静态 sessionid | ||
| 360 | +const sessionid = 'static_value' | ||
| 361 | + | ||
| 362 | +// ❌ 字段名错误 | ||
| 363 | +config.headers.sessionid = sessionid | ||
| 364 | + | ||
| 365 | +// ❌ 不设置到请求头 | ||
| 366 | +// 只读取了 sessionid,但没有设置到 headers | ||
| 367 | +``` | ||
| 368 | + | ||
| 369 | +--- | ||
| 370 | + | ||
| 371 | +## ⚠️ 常见陷阱 | ||
| 372 | + | ||
| 373 | +### 1. NutUI textarea 样式无法覆盖 | ||
| 374 | + | ||
| 375 | +**问题**: | ||
| 376 | +```vue | ||
| 377 | +<!-- ❌ 深度选择器无效 --> | ||
| 378 | +<nut-textarea v-model="content" class="custom-textarea" /> | ||
| 379 | +``` | ||
| 380 | + | ||
| 381 | +**解决**: | ||
| 382 | +```vue | ||
| 383 | +<!-- ✅ 使用原生 textarea --> | ||
| 384 | +<textarea | ||
| 385 | + v-model="content" | ||
| 386 | + class="custom-textarea" | ||
| 387 | + maxlength="200" | ||
| 388 | +/> | ||
| 389 | +``` | ||
| 390 | + | ||
| 391 | +### 2. IconFont 动态切换不响应 | ||
| 392 | + | ||
| 393 | +**问题**: | ||
| 394 | +```vue | ||
| 395 | +<!-- ❌ 图标不更新 --> | ||
| 396 | +<IconFont :name="iconName" /> | ||
| 397 | +``` | ||
| 398 | + | ||
| 399 | +**解决**: | ||
| 400 | +```vue | ||
| 401 | +<!-- ✅ 添加 key 强制重新渲染 --> | ||
| 402 | +<IconFont :name="iconName" :key="iconName" /> | ||
| 403 | +``` | ||
| 404 | + | ||
| 405 | +### 3. 嵌套弹窗层级冲突 | ||
| 406 | + | ||
| 407 | +**问题**: | ||
| 408 | +```vue | ||
| 409 | +<!-- ❌ 真机上内层弹窗被外层弹窗遮挡 --> | ||
| 410 | +<PlanPopup> | ||
| 411 | + <nut-popup v-model:visible="showPicker"> | ||
| 412 | + <nut-picker /> | ||
| 413 | + </nut-popup> | ||
| 414 | +</PlanPopup> | ||
| 415 | +``` | ||
| 416 | + | ||
| 417 | +**解决**: | ||
| 418 | +```vue | ||
| 419 | +<!-- ✅ 将内层弹窗提升到外层 --> | ||
| 420 | +<PlanPopup /> | ||
| 421 | +<nut-popup | ||
| 422 | + v-model:visible="showPicker" | ||
| 423 | + :z-index="9999" | ||
| 424 | + :overlay="true" | ||
| 425 | +> | ||
| 426 | + <nut-picker /> | ||
| 427 | +</nut-popup> | ||
| 428 | +``` | ||
| 429 | + | ||
| 430 | +### 4. SVG 图标加载失败 | ||
| 431 | + | ||
| 432 | +**问题**: | ||
| 433 | +```javascript | ||
| 434 | +// ❌ 字符串路径导致 500 错误 | ||
| 435 | +const icons = { | ||
| 436 | + pdf: '/assets/images/icon/doc/pdf.svg' | ||
| 437 | +} | ||
| 438 | +``` | ||
| 439 | + | ||
| 440 | +**解决**: | ||
| 441 | +```javascript | ||
| 442 | +// ✅ 使用 import 导入 | ||
| 443 | +import pdfIcon from '@/assets/images/icon/doc/pdf.svg' | ||
| 444 | +const icons = { pdf: pdfIcon } | ||
| 445 | +``` | ||
| 446 | + | ||
| 447 | +--- | ||
| 448 | + | ||
| 449 | +## 📦 Composable 抽取原则 | ||
| 450 | + | ||
| 451 | +**"第 3 次出现原则"**: 代码重复 3 次时必须抽取 | ||
| 452 | + | ||
| 453 | +### 判断标准 | ||
| 454 | + | ||
| 455 | +| 场景 | 抽取条件 | 抽取目标 | | ||
| 456 | +|------|---------|----------| | ||
| 457 | +| 代码重复 | ≥ 2 次 | 警惕 | | ||
| 458 | +| 代码重复 | ≥ 3 次 | **必须抽取** | | ||
| 459 | +| v-for 模板 | > 5 行 | 提取列表项组件 | | ||
| 460 | +| 组件模板 | > 150 行 | 拆分组件 | | ||
| 461 | +| 函数长度 | > 50 行 | 拆分函数 | | ||
| 462 | + | ||
| 463 | +### 项目中的 Composables | ||
| 464 | + | ||
| 465 | +| Composable | 用途 | | ||
| 466 | +|-----------|------| | ||
| 467 | +| `useSectionList` | 分组列表管理 | | ||
| 468 | +| `useFileOperation` | 文件下载、预览、打开 | | ||
| 469 | +| `useListItemClick` | 统一的列表点击处理 | | ||
| 470 | + | ||
| 471 | +**抽取示例**: | ||
| 472 | +```javascript | ||
| 473 | +// ✅ GOOD - 创建 useFileOperation Composable | ||
| 474 | +export function useFileOperation() { | ||
| 475 | + const viewFile = async (file) => { | ||
| 476 | + // 文件预览逻辑 | ||
| 477 | + } | ||
| 478 | + const downloadFile = async (file) => { | ||
| 479 | + // 文件下载逻辑 | ||
| 480 | + } | ||
| 481 | + return { viewFile, downloadFile } | ||
| 482 | +} | ||
| 483 | + | ||
| 484 | +// 使用 | ||
| 485 | +const { viewFile } = useFileOperation() | ||
| 486 | +``` | ||
| 487 | + | ||
| 488 | +--- | ||
| 489 | + | ||
| 490 | +## 🔧 性能优化 | ||
| 491 | + | ||
| 492 | +### 响应式数据优化 | ||
| 493 | + | ||
| 494 | +```javascript | ||
| 495 | +import { shallowRef, markRaw } from 'vue' | ||
| 496 | + | ||
| 497 | +// ❌ BAD - 深度响应式 | ||
| 498 | +const menuItems = ref([ | ||
| 499 | + { icon: IconFont, name: 'heart' } // Vue 会深度代理组件对象 | ||
| 500 | +]) | ||
| 501 | + | ||
| 502 | +// ✅ GOOD - 浅层响应式 | ||
| 503 | +const menuItems = shallowRef([ | ||
| 504 | + { icon: markRaw(IconFont), name: 'heart' } // 避免深度代理 | ||
| 505 | +]) | ||
| 506 | +``` | ||
| 507 | + | ||
| 508 | +### 图片优化 | ||
| 509 | + | ||
| 510 | +```javascript | ||
| 511 | +// CDN 图片优化 | ||
| 512 | +function optimizeImageUrl(url, width = 750, quality = 70) { | ||
| 513 | + if (!url || !url.includes('cdn.ipadbiz.cn')) { | ||
| 514 | + return url | ||
| 515 | + } | ||
| 516 | + return `${url}?imageMogr2/thumbnail/${width}x/quality/${quality}` | ||
| 517 | +} | ||
| 518 | +``` | ||
| 519 | + | ||
| 520 | +--- | ||
| 521 | + | ||
| 522 | +## 📝 代码规范 | ||
| 523 | + | ||
| 524 | +### JSDoc 注释(必须) | ||
| 525 | + | ||
| 526 | +```javascript | ||
| 527 | +/** | ||
| 528 | + * 获取产品列表 | ||
| 529 | + * | ||
| 530 | + * @description 从 API 获取产品列表数据 | ||
| 531 | + * @param {Object} params - 查询参数 | ||
| 532 | + * @param {number} params.page - 页码 | ||
| 533 | + * @param {number} params.limit - 每页数量 | ||
| 534 | + * @returns {Promise<Object>} 产品列表数据 | ||
| 535 | + * | ||
| 536 | + * @example | ||
| 537 | + * const list = await getProductList({ page: 1, limit: 10 }) | ||
| 538 | + */ | ||
| 539 | +export async function getProductList(params) { | ||
| 540 | + // ... | ||
| 541 | +} | ||
| 542 | +``` | ||
| 543 | + | ||
| 544 | +### 错误处理 | ||
| 545 | + | ||
| 546 | +```javascript | ||
| 547 | +// ✅ GOOD - 统一错误处理 | ||
| 548 | +try { | ||
| 549 | + await someAPI() | ||
| 550 | +} catch (err) { | ||
| 551 | + console.error('操作失败:', err) | ||
| 552 | + Taro.showToast({ | ||
| 553 | + title: '操作失败,请重试', | ||
| 554 | + icon: 'none' | ||
| 555 | + }) | ||
| 556 | +} | ||
| 557 | +``` | ||
| 558 | + | ||
| 559 | +--- | ||
| 560 | + | ||
| 561 | +## 🔍 调试技巧 | ||
| 562 | + | ||
| 563 | +### 检查网络请求 | ||
| 564 | + | ||
| 565 | +```javascript | ||
| 566 | +// 检查网络类型 | ||
| 567 | +const { networkType } = await Taro.getNetworkType() | ||
| 568 | +if (networkType === 'none') { | ||
| 569 | + console.error('网络未连接') | ||
| 570 | +} | ||
| 571 | +``` | ||
| 572 | + | ||
| 573 | +### 性能监控 | ||
| 574 | + | ||
| 575 | +```javascript | ||
| 576 | +import { useReady } from '@tarojs/taro' | ||
| 577 | + | ||
| 578 | +const startTime = Date.now() | ||
| 579 | + | ||
| 580 | +useReady(() => { | ||
| 581 | + const loadTime = Date.now() - startTime | ||
| 582 | + console.log('页面加载耗时:', loadTime) | ||
| 583 | +}) | ||
| 584 | +``` | ||
| 585 | + | ||
| 586 | +--- | ||
| 587 | + | ||
| 588 | +## 📚 参考资源 | ||
| 589 | + | ||
| 590 | +- [Taro 官方文档](https://docs.taro.zone/) | ||
| 591 | +- [微信小程序开发文档](https://developers.weixin.qq.com/miniprogram/dev/framework/) | ||
| 592 | +- [项目经验教训总结](./lessons-learned.md) | ||
| 593 | +- [Taro 开发规范](~/.claude/rules/taro-patterns.md) | ||
| 594 | +- [小程序开发检查清单](~/.claude/rules/miniprogram-checklist.md) | ||
| 595 | + | ||
| 596 | +--- | ||
| 597 | + | ||
| 598 | +**最后更新**: 2026-02-03 | ||
| 599 | +**维护者**: Claude Code | ||
| 600 | +**项目**: Manulife WeApp |
-
Please register or login to post a comment