# 开发最佳实践与常见坑

> 综合官方文档与社区经验

## setData 性能优化

setData 是小程序性能的关键瓶颈，因为数据需要从逻辑层（JS 线程）序列化后传输到视图层（渲染线程）。

### 原则

1. **减少数据量**：只传需要更新的字段
```javascript
// ❌ 错误：传整个列表
this.setData({ list: this.data.list })

// ✅ 正确：只更新变化的项
this.setData({ 'list[2].name': 'new name' })
```

2. **减少调用频率**：合并多次 setData
```javascript
// ❌ 错误：多次调用
this.setData({ a: 1 })
this.setData({ b: 2 })
this.setData({ c: 3 })

// ✅ 正确：合并为一次
this.setData({ a: 1, b: 2, c: 3 })
```

3. **后台页面不要 setData**
```javascript
// ❌ 错误：页面隐藏后仍在 setData（如定时器）
onShow() {
  this._timer = setInterval(() => {
    this.setData({ time: Date.now() })
  }, 1000)
},
// ✅ 正确：页面隐藏时停止
onHide() {
  clearInterval(this._timer)
}
```

4. **避免在 onPageScroll 中 setData**
```javascript
// ❌ 错误
onPageScroll(e) {
  this.setData({ scrollTop: e.scrollTop })
}

// ✅ 正确：用 WXS 响应事件或节流
onPageScroll: throttle(function(e) {
  if (this._needUpdate) {
    this.setData({ isTop: e.scrollTop < 100 })
  }
}, 100)
```

5. **大列表用纯数据字段**
```javascript
Component({
  options: { pureDataPattern: /^_/ },
  data: {
    displayList: [],  // 用于渲染
    _rawList: []      // 纯数据，不传输到视图层
  }
})
```

## 分包加载

### 基本分包

```json
// app.json
{
  "pages": [
    "pages/index/index",
    "pages/logs/logs"
  ],
  "subpackages": [
    {
      "root": "packageA",
      "name": "pack-a",
      "pages": [
        "pages/cat/cat",
        "pages/dog/dog"
      ]
    },
    {
      "root": "packageB",
      "name": "pack-b",
      "pages": [
        "pages/apple/apple"
      ]
    }
  ]
}
```

**限制**：
- 整个小程序所有分包大小不超过 20MB（使用分包时）
- 单个分包/主包大小不超过 2MB
- tabBar 页面必须在主包

### 独立分包

不依赖主包即可运行，适合独立功能页面（如活动页）。

```json
{
  "subpackages": [
    {
      "root": "packageIndependent",
      "pages": ["pages/activity/activity"],
      "independent": true
    }
  ]
}
```

**注意**：独立分包中不能使用主包的公共资源（js/组件/样式）。

### 分包预下载

```json
{
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["packageA"]
    },
    "pages/logs/logs": {
      "network": "wifi",
      "packages": ["packageB"]
    }
  }
}
```

## 小程序与 H5 的关键差异

| 特性 | H5 | 小程序 |
|------|-----|--------|
| DOM 操作 | 支持 document/window | ❌ 不支持 |
| BOM | 支持 | ❌ 不支持 |
| 路由 | URL hash/history | 页面栈（最多 10 层） |
| 样式 | 完整 CSS | WXSS（部分 CSS 不支持） |
| 脚本 | 完整 JS + Web API | JS（无 DOM API） |
| 渲染 | 单线程 | 双线程（逻辑层 + 视图层） |
| 网络请求 | fetch/XMLHttpRequest | wx.request（需配置域名） |
| 本地存储 | localStorage | wx.setStorage（10MB） |
| Cookie | 支持 | ❌ 不支持（需自行管理） |
| 动态创建元素 | 支持 | ❌ 不支持 |
| eval / new Function | 支持 | ❌ 不支持 |
| SVG | 支持 | 部分支持（image src 可用） |

### 常见迁移坑

1. **没有 Cookie**：登录态需要自行通过 header 传递 token
2. **没有 DOM**：不能用 jQuery、不能 `document.getElementById`
3. **不支持动态执行代码**：`eval()`、`new Function()` 都不可用
4. **样式差异**：
   - 不支持 `*` 通配符选择器
   - 不支持 `>` `+` `~` 等关系选择器（部分版本已支持）
   - 不支持 `@media` 的部分写法
   - 不支持 `position: fixed` 在某些场景下的表现
5. **页面栈限制**：最多 10 层，超过后 navigateTo 会失败
6. **包大小限制**：主包 2MB，总包 20MB
7. **网络请求域名白名单**：必须在管理后台配置
8. **不支持 npm 直接引入**：需要通过开发者工具构建 npm

## TypeScript 支持

小程序原生支持 TypeScript：

```json
// tsconfig.json（项目根目录）
{
  "compilerOptions": {
    "strict": true,
    "target": "ES2017",
    "module": "ESNext",
    "moduleResolution": "Node",
    "lib": ["ES2017"],
    "typeRoots": ["./typings"]
  },
  "include": ["**/*.ts"],
  "exclude": ["node_modules"]
}
```

```typescript
// 页面 .ts 文件
Page({
  data: {
    msg: 'Hello' as string,
    list: [] as Array<{ id: number; name: string }>
  },
  onLoad(options: Record<string, string | undefined>) {
    const id = options.id
  }
})

// 组件 .ts 文件
Component({
  properties: {
    title: { type: String, value: '' }
  },
  data: {
    count: 0 as number
  },
  methods: {
    increment() {
      this.setData({ count: this.data.count + 1 })
    }
  }
})
```

## npm 支持

1. 在小程序根目录执行 `npm install`
2. 在开发者工具中：工具 → 构建 npm
3. 构建后会生成 `miniprogram_npm` 目录
4. 使用：`const dayjs = require('dayjs')`

**注意**：
- 不是所有 npm 包都能在小程序中使用（不能依赖 Node.js 内置模块或浏览器 API）
- 每次 `npm install` 后都需要重新构建 npm

## 自定义 tabBar

```json
// app.json
{
  "tabBar": {
    "custom": true,
    "list": [
      { "pagePath": "pages/index/index", "text": "首页" },
      { "pagePath": "pages/mine/mine", "text": "我的" }
    ]
  }
}
```

在根目录创建 `custom-tab-bar/` 组件：
```
custom-tab-bar/
├── index.js
├── index.json
├── index.wxml
└── index.wxss
```

## 骨架屏

开发者工具支持自动生成骨架屏：
1. 在模拟器中预览页面
2. 点击模拟器右下角「...」→「生成骨架屏」
3. 会自动生成 `页面名.skeleton.wxml` 和 `页面名.skeleton.wxss`

```json
// 页面 json 中引用
{
  "initialRenderingCache": "static"
}
```

## 常见审核被拒原因

1. **功能不完整**：提交审核时确保所有功能可用
2. **测试账号未提供**：需要登录的小程序必须提供测试账号
3. **类目不符**：选择的服务类目与实际功能不匹配
4. **诱导分享/关注**：不能强制用户分享或关注公众号才能使用
5. **虚拟支付**：iOS 不允许虚拟商品使用微信支付（需走 IAP）
6. **内容违规**：UGC 内容需要内容安全检测
7. **隐私协议**：需要配置隐私保护指引
8. **授权滥用**：不能在首页就弹出授权请求，需要在使用时才请求

## 调试技巧

```javascript
// 真机调试日志
const log = wx.getRealtimeLogManager()
log.info('info message')
log.warn('warn message')
log.error('error message')

// 性能监控
const performance = wx.getPerformance()
const observer = performance.createObserver((entryList) => {
  console.log(entryList.getEntries())
})
observer.observe({ entryTypes: ['render', 'script', 'navigation'] })
```

## 在线查询

- 性能优化：https://developers.weixin.qq.com/miniprogram/dev/framework/performance/tips.html
- 分包加载：https://developers.weixin.qq.com/miniprogram/dev/framework/subpackages.html
- npm 支持：https://developers.weixin.qq.com/miniprogram/dev/devtools/npm.html
- 自定义 tabBar：https://developers.weixin.qq.com/miniprogram/dev/framework/ability/custom-tabbar.html
