重新学习前端之小程序

小程序

一、微信小程序基础

1. 什么是微信小程序?

定义

微信小程序是一种不需要下载安装即可使用的应用,它实现了应用"触手可及"的梦想,用户扫一扫或搜一下即可打开应用。小程序运行在微信客户端内,基于自研的渲染引擎和 JavaScript 运行环境。

原理

小程序采用双线程架构:

  • 逻辑层(AppService):运行 JavaScript 代码,处理业务逻辑,使用 JSCore(iOS)或 V8(Android)
  • 渲染层(WebView):负责页面渲染,使用 WebView
  • 两个线程通过微信客户端进行通信,使用 postMessageonMessage 进行数据传递
scss 复制代码
┌─────────────────────────────────────────────────────────────┐
│                        微信客户端                             │
├──────────────────────┬──────────────────────────────────────┤
│    渲染层 (WebView)   │        逻辑层 (JSCore/V8)            │
│  - WXML 结构         │        - JavaScript 代码             │
│  - WXSS 样式         │        - 业务逻辑处理                 │
│  - 页面渲染          │        - 数据管理                     │
└──────────┬───────────┴──────────────┬───────────────────────┘
           │                          │
           └──────────┬───────────────┘
                      │
              微信客户端 Native 桥接
           (postMessage / onMessage)

核心特点

  • 无需安装,即用即走
  • 依托微信生态,天然具备社交属性
  • 跨平台(iOS/Android 体验一致)
  • 受限的运行环境(不能使用 DOM/BOM API)

示例

javascript 复制代码
// app.js - 小程序入口文件
App({
  onLaunch() {
    console.log('小程序启动')
  },
  globalData: {
    userInfo: null
  }
})

// pages/index/index.js - 页面文件
Page({
  data: {
    message: 'Hello Mini Program'
  },
  onLoad() {
    console.log('页面加载')
  }
})

常见误区

  • ❌ 小程序就是 H5 网页 → ✅ 小程序是独立的技术体系,有专用的语言和运行环境
  • ❌ 小程序可以随意跳转外部链接 → ✅ 小程序只能访问配置好的业务域名
  • ❌ 小程序没有大小限制 → ✅ 主包限制 2MB,总包限制 20MB

2. 微信小程序开发流程是什么?

实现步骤

步骤一:注册账号

  1. 访问 微信公众平台
  2. 选择"小程序"类型进行注册
  3. 填写邮箱、密码等基本信息
  4. 完成主体信息登记(个人/企业)

步骤二:获取 AppID

  1. 登录微信公众平台
  2. 进入"开发" → "开发管理" → "开发设置"
  3. 复制 AppID(小程序唯一标识)

步骤三:安装开发工具

  1. 下载 微信开发者工具
  2. 使用微信扫码登录
  3. 创建新项目,填入 AppID

步骤四:项目结构

perl 复制代码
miniprogram/
├── app.js              # 小程序逻辑
├── app.json            # 小程序公共配置
├── app.wxss            # 小程序公共样式表
├── pages/              # 页面目录
│   ├── index/
│   │   ├── index.js
│   │   ├── index.json
│   │   ├── index.wxml
│   │   └── index.wxss
│   └── logs/
│       ├── logs.js
│       ├── logs.json
│       ├── logs.wxml
│       └── logs.wxss
├── utils/              # 工具函数
└── components/         # 自定义组件

步骤五:开发与调试

javascript 复制代码
// pages/index/index.js
Page({
  data: {
    count: 0
  },
  addCount() {
    this.setData({
      count: this.data.count + 1
    })
  }
})
xml 复制代码
<!-- pages/index/index.wxml -->
<view class="container">
  <text>计数: {{count}}</text>
  <button bindtap="addCount">+1</button>
</view>

步骤六:发布上线

  1. 点击"上传"按钮上传代码
  2. 登录微信公众平台提交审核
  3. 审核通过后发布上线

最佳实践

  • 开发阶段使用测试号,避免频繁扫码
  • 合理使用版本管理(Git)
  • 提交前做好真机测试
  • 注意代码包体积限制

3. 微信小程序如何注册?

定义

微信小程序注册分为两种含义:

  1. 平台注册:在微信公众平台注册小程序账号
  2. 用户注册:小程序内用户授权注册流程

平台注册流程

  1. 访问 mp.weixin.qq.com
  2. 点击"立即注册"
  3. 选择"小程序"类型
  4. 填写邮箱、密码、验证码
  5. 邮箱激活
  6. 信息登记(主体类型选择)
  7. 完成注册

主体类型对比

主体类型 适用对象 能力差异
个人 个人开发者 无法开通微信支付、部分接口受限
企业 公司/企业 完整能力支持
政府 政府机构 完整能力支持
媒体 新闻媒体 完整能力支持
其他组织 非营利组织等 部分能力支持

用户注册/登录实现

javascript 复制代码
// 小程序登录流程
Page({
  async login() {
    // 1. 获取 code
    const { code } = await wx.login()
    
    // 2. 发送 code 到开发者服务器
    const res = await wx.request({
      url: 'https://your-server.com/api/login',
      method: 'POST',
      data: { code }
    })
    
    // 3. 获取自定义登录态
    const { token } = res.data
    wx.setStorageSync('token', token)
  },
  
  // 获取用户信息(需要用户授权)
  async getUserProfile() {
    const res = await wx.getUserProfile({
      desc: '用于完善用户资料'
    })
    console.log(res.userInfo)
  }
})

后端登录验证流程

javascript 复制代码
// Node.js 后端示例
const request = require('request')

app.post('/api/login', async (req, res) => {
  const { code } = req.body
  
  // 调用微信接口获取 openid 和 session_key
  const url = `https://api.weixin.qq.com/sns/jscode2session?appid=${APPID}&secret=${SECRET}&js_code=${code}&grant_type=authorization_code`
  
  request(url, (error, response, body) => {
    const { openid, session_key } = JSON.parse(body)
    
    // 生成自定义登录态(token)
    const token = generateToken(openid)
    
    // 存储 session_key(不能返回给客户端)
    saveSession(token, session_key)
    
    res.json({ token })
  })
})

常见误区

  • wx.getUserInfo 可以直接获取用户信息 → ✅ 已废弃,需要使用 wx.getUserProfile 或按钮授权
  • ❌ session_key 可以返回给客户端 → ✅ session_key 是敏感信息,只能保存在服务端
  • ❌ code 可以多次使用 → ✅ code 只能使用一次,有效期 5 分钟

4. 微信小程序配置详解

4.1 app.json 全局配置

定义

app.json 是小程序的全局配置文件,用于配置小程序的页面路径、窗口表现、网络超时时间、底部 tab 等。

完整配置示例

json 复制代码
{
  "pages": [
    "pages/index/index",
    "pages/logs/logs",
    "pages/user/user"
  ],
  "window": {
    "navigationBarTitleText": "小程序演示",
    "navigationBarBackgroundColor": "#ffffff",
    "navigationBarTextStyle": "black",
    "backgroundColor": "#eeeeee",
    "backgroundTextStyle": "dark",
    "enablePullDownRefresh": true
  },
  "tabBar": {
    "color": "#999999",
    "selectedColor": "#ff6633",
    "backgroundColor": "#ffffff",
    "borderStyle": "black",
    "position": "bottom",
    "list": [
      {
        "pagePath": "pages/index/index",
        "text": "首页",
        "iconPath": "images/home.png",
        "selectedIconPath": "images/home-active.png"
      },
      {
        "pagePath": "pages/user/user",
        "text": "我的",
        "iconPath": "images/user.png",
        "selectedIconPath": "images/user-active.png"
      }
    ]
  },
  "networkTimeout": {
    "request": 10000,
    "downloadFile": 10000
  },
  "style": "v2",
  "sitemapLocation": "sitemap.json",
  "lazyCodeLoading": "requiredComponents"
}

核心配置项说明

配置项 类型 必填 说明
pages Array 页面路径列表,第一项为首页
window Object 全局默认窗口表现
tabBar Object 底部 tab 栏配置
subPackages Array 分包加载配置
workers String Worker 代码放置目录
networkTimeout Object 网络超时时间
functionalPages Boolean 是否启用插件功能页
plugins Object 使用插件配置
permission Object 接口权限设置

pages 配置规则

json 复制代码
{
  "pages": [
    "pages/index/index",      // 第一个页面是首页
    "pages/detail/index",
    "pages/user/index"
  ]
}
// 注意:只需要写路径,不需要写文件扩展名
// 开发者工具会自动读取路径下的 .js .json .wxml .wxss 四个文件

常见误区

  • ❌ pages 数组最后一个页面不能加逗号 → ✅ JSON 不允许尾逗号
  • ❌ 新增页面不需要注册 → ✅ 所有页面必须在 pages 中注册
  • ❌ 修改 pages 顺序不会改变首页 → ✅ pages 第一项就是首页
4.2 page.json 页面配置

定义

page.json 用于配置页面窗口表现,只能配置 window 配置项的内容,会覆盖 app.json 的 window 配置。

示例

json 复制代码
{
  "navigationBarTitleText": "用户中心",
  "navigationBarBackgroundColor": "#07C160",
  "navigationBarTextStyle": "white",
  "backgroundColor": "#f7f7f7",
  "enablePullDownRefresh": true,
  "onReachBottomDistance": 50,
  "pageOrientation": "portrait",
  "usingComponents": {
    "custom-header": "/components/custom-header/index",
    "custom-footer": "/components/custom-footer/index"
  }
}
4.3 project.config.json 项目配置

定义

项目配置文件,保存开发者工具的个性化设置,如界面颜色、编译配置等。

json 复制代码
{
  "description": "项目配置文件",
  "packOptions": {
    "ignore": [],
    "include": []
  },
  "setting": {
    "bundle": false,
    "userConfirmedBundleSwitch": false,
    "urlCheck": true,
    "scopeDataCheck": false,
    "coverView": true,
    "es6": true,
    "postcss": true,
    "compileHotReLoad": false,
    "lazyloadPlaceholderEnable": false,
    "preloadBackgroundData": false,
    "minified": true,
    "autoAudits": false,
    "newFeature": false,
    "uglifyFileName": false,
    "uploadWithSourceMap": true,
    "useIsolateContext": true,
    "nodeModules": false,
    "enhance": true,
    "useMultiFrameRuntime": true,
    "useApiHook": true,
    "showShadowRootInWxmlPanel": true,
    "packNpmManually": false,
    "enableEngp": false,
    "packNpmRelationList": [],
    "minifyWXSS": true,
    "showES6CompileOption": false,
    "minifyWXML": true,
    "babelSetting": {
      "ignore": [],
      "disablePlugins": [],
      "outputPath": ""
    }
  },
  "compileType": "miniprogram",
  "libVersion": "2.19.4",
  "appid": "wx1234567890abcdef",
  "projectname": "my-miniprogram",
  "condition": {}
}
4.4 sitemap.json 索引配置

定义

用于配置小程序页面是否允许微信索引。微信会基于 sitemap 的配置对小程序页面进行索引。

示例

json 复制代码
{
  "rules": [
    {
      "action": "allow",
      "page": "*"
    },
    {
      "action": "disallow",
      "page": "pages/debug/debug"
    }
  ]
}

规则说明

  • action: allow 允许索引,disallow 不允许索引
  • page: 页面路径,* 表示所有页面
  • 规则按顺序匹配,前面优先

5. 微信小程序登录流程

定义

微信小程序登录是基于 OAuth 2.0 的授权登录流程,通过 wx.login() 获取临时登录凭证 code,换取用户唯一标识 openid 和会话密钥 session_key。

登录时序图

scss 复制代码
┌──────┐     ┌──────┐     ┌──────┐     ┌────────┐
│小程序 │     │开发者 │     │微信  │     │ 业务   │
│      │     │服务器 │     │服务器 │     │ 数据库  │
└──┬───┘     └──┬───┘     └──┬───┘     └───┬────┘
   │            │            │             │
   │ wx.login() │            │             │
   │───┬───>    │            │             │
   │   │ code   │            │             │
   │   <───┤    │            │             │
   │            │            │             │
   │  request   │            │             │
   │───────────>│            │             │
   │  {code}    │            │             │
   │            │            │             │
   │            │ code2session               │
   │            │───────────>│             │
   │            │            │             │
   │            │ openid,session_key        │
   │            │<───────────│             │
   │            │            │             │
   │            │ 生成3rd_session(token)    │
   │            │──────────────────────────>│
   │            │            │             │
   │            │<──────────────────────────│
   │            │   token                   │
   │   <────────│            │             │
   │  token     │            │             │
   │            │            │             │
   │ 存储 token │            │             │

完整实现

javascript 复制代码
// 小程序端
class AuthService {
  // 静默登录
  static async silentLogin() {
    try {
      // 1. 获取 code
      const { code } = await wx.login()
      if (!code) {
        throw new Error('登录失败,无法获取 code')
      }
      
      // 2. 调用后端接口
      const res = await wx.request({
        url: 'https://api.example.com/auth/login',
        method: 'POST',
        data: { code }
      })
      
      // 3. 存储登录态
      const { token, userInfo } = res.data
      wx.setStorageSync('token', token)
      wx.setStorageSync('userInfo', userInfo)
      
      return { token, userInfo }
    } catch (error) {
      console.error('登录失败:', error)
      throw error
    }
  }
  
  // 检查登录状态
  static async checkLogin() {
    const token = wx.getStorageSync('token')
    if (!token) {
      return this.silentLogin()
    }
    
    // 验证 token 是否有效
    try {
      const res = await wx.request({
        url: 'https://api.example.com/auth/check',
        method: 'POST',
        header: { Authorization: `Bearer ${token}` }
      })
      return res.data
    } catch {
      // token 失效,重新登录
      return this.silentLogin()
    }
  }
  
  // 获取用户信息
  static async getUserProfile() {
    try {
      const res = await wx.getUserProfile({
        desc: '用于完善个人资料'
      })
      return res.userInfo
    } catch (error) {
      console.error('用户拒绝授权')
      throw error
    }
  }
}

// 使用
App({
  async onLaunch() {
    await AuthService.silentLogin()
  }
})

后端实现(Node.js)

javascript 复制代码
const crypto = require('crypto')

class WechatAuthService {
  constructor(appid, secret) {
    this.appid = appid
    this.secret = secret
  }
  
  // code2session
  async code2Session(code) {
    const url = `https://api.weixin.qq.com/sns/jscode2session`
    const params = {
      appid: this.appid,
      secret: this.secret,
      js_code: code,
      grant_type: 'authorization_code'
    }
    
    const res = await httpsGet(url, params)
    return res
  }
  
  // 登录接口
  async login(code) {
    // 1. 获取 openid 和 session_key
    const { openid, session_key, unionid } = await this.code2Session(code)
    
    // 2. 生成自定义登录态
    const token = this.generateToken(openid, session_key)
    
    // 3. 存储 session_key
    await redis.set(`session:${token}`, session_key, { EX: 7200 })
    
    // 4. 获取或创建用户
    const user = await this.getOrCreateUser(openid, unionid)
    
    return { token, userInfo: user }
  }
  
  generateToken(openid, sessionKey) {
    const content = `${openid}:${sessionKey}:${Date.now()}`
    return crypto.createHash('sha256').update(content).digest('hex')
  }
}

最佳实践

  • 登录态有效期建议 2 小时
  • 使用 token 机制,不要将 session_key 返回客户端
  • 登录失败要有重试机制
  • 用户信息获取需要用户主动授权

常见误区

  • ❌ 每次打开小程序都重新登录 → ✅ 应检查本地 token 是否有效
  • ❌ 使用 session_key 作为登录凭证 → ✅ session_key 敏感,应生成第三方 session
  • ❌ 静默登录获取用户头像昵称 → ✅ 需要用户授权才能获取

6. 微信小程序支付流程

定义

微信支付是小程序内完成交易的核心能力,需要商户号、后端服务器配合,遵循统一的支付流程。

支付流程图

scss 复制代码
┌──────┐     ┌──────┐     ┌──────┐     ┌──────┐
│小程序 │     │商户  │     │微信  │     │ 银行  │
│      │     │服务器 │     │支付  │     │      │
└──┬───┘     └──┬───┘     └──┬───┘     └───┬──┘
   │            │            │             │
   │ 发起支付   │            │             │
   │───────────>│            │             │
   │            │            │             │
   │            │ 请求统一下单               │
   │            │───────────>│             │
   │            │            │             │
   │            │ prepay_id │             │
   │            │<───────────│             │
   │            │            │             │
   │ 支付参数   │            │             │
   │<───────────│            │             │
   │            │            │             │
   │ wx.requestPayment()    │             │
   │            │            │             │
   │────────────────────────>│             │
   │            │            │             │
   │ 支付结果   │            │             │
   │<────────────────────────│             │
   │            │            │             │
   │ 通知支付结果               │             │
   │───────────>│            │             │
   │            │            │             │
   │            │ 支付回调通知               │
   │            │<───────────│             │
   │            │            │             │
   │            │ 发货/处理业务              │

小程序端实现

javascript 复制代码
// 发起支付
async function doPayment(orderId) {
  // 1. 请求后端获取支付参数
  const { data } = await wx.request({
    url: 'https://api.example.com/pay/create',
    method: 'POST',
    data: { orderId }
  })
  
  const { 
    timeStamp, 
    nonceStr, 
    package: pkg, 
    signType, 
    paySign 
  } = data
  
  // 2. 调起支付
  try {
    await wx.requestPayment({
      timeStamp,
      nonceStr,
      package: pkg,
      signType: signType || 'RSA',
      paySign,
      success(res) {
        console.log('支付成功', res)
        // 跳转到成功页面
        wx.redirectTo({ url: '/pages/pay-success/index' })
      },
      fail(err) {
        if (err.errMsg.includes('cancel')) {
          console.log('用户取消支付')
        } else {
          console.error('支付失败', err)
        }
      }
    })
  } catch (error) {
    console.error('支付异常', error)
  }
}

后端统一下单(Node.js)

javascript 复制代码
const crypto = require('crypto')
const fs = require('fs')

class WechatPay {
  constructor(config) {
    this.appid = config.appid
    this.mchid = config.mchid
    this.privateKey = fs.readFileSync(config.privateKeyPath)
    this.serialNo = config.serialNo
  }
  
  // 生成签名
  sign(data) {
    const sign = crypto.createSign('RSA-SHA256')
    sign.update(data)
    return sign.sign(this.privateKey, 'base64')
  }
  
  // 统一下单
  async createOrder(params) {
    const { outTradeNo, description, amount, openid } = params
    
    const orderData = {
      appid: this.appid,
      mchid: this.mchid,
      description,
      out_trade_no: outTradeNo,
      notify_url: 'https://api.example.com/pay/notify',
      amount: {
        total: amount, // 单位:分
        currency: 'CNY'
      },
      payer: {
        openid
      }
    }
    
    // 调用微信 API
    const result = await this.requestPayAPI('/v3/pay/transactions/jsapi', orderData)
    
    return {
      timeStamp: String(Math.floor(Date.now() / 1000)),
      nonceStr: this.generateNonceStr(),
      package: `prepay_id=${result.prepay_id}`,
      signType: 'RSA',
      paySign: this.generatePaySign(result.prepay_id)
    }
  }
  
  generatePaySign(prepayId) {
    const timeStamp = String(Math.floor(Date.now() / 1000))
    const nonceStr = this.generateNonceStr()
    const package = `prepay_id=${prepayId}`
    
    const message = `${this.appid}\n${timeStamp}\n${nonceStr}\n${package}\n`
    return this.sign(message)
  }
}

支付回调处理

javascript 复制代码
// 处理支付回调
app.post('/pay/notify', async (req, res) => {
  const { body } = req
  
  // 1. 验证签名
  const isValid = verifySignature(req.headers, body)
  if (!isValid) {
    return res.status(401).send('签名验证失败')
  }
  
  // 2. 解析回调数据
  const data = decryptCallback(body)
  
  // 3. 更新订单状态
  await updateOrderStatus(data.out_trade_no, 'paid')
  
  // 4. 返回成功响应
  res.json({ code: 'SUCCESS', message: '成功' })
})

最佳实践

  • 金额单位是分,注意转换
  • 回调必须验证签名
  • 订单号要唯一
  • 处理重复回调(幂等性)
  • 支付结果以回调为准,不要仅依赖前端结果

常见误区

  • ❌ 前端可以修改支付金额 → ✅ 金额由后端决定
  • ❌ 支付成功立即发货 → ✅ 以回调为准
  • ❌ 回调只处理一次 → ✅ 可能多次回调,需幂等处理

7. 微信小程序分享功能

定义

小程序分享是指将小程序页面或内容分享给微信好友或微信群的能力,包括页面分享和按钮分享两种方式。

实现方式

方式一:页面分享(右上角菜单)

javascript 复制代码
Page({
  onShareAppMessage() {
    return {
      title: '分享标题',
      path: '/pages/index/index?shareFrom=123',
      imageUrl: 'https://example.com/share.png',
      promise: new Promise((resolve) => {
        // 异步获取分享数据
        setTimeout(() => {
          resolve({
            title: '异步分享标题',
            path: '/pages/index/index?shareFrom=456'
          })
        }, 100)
      })
    }
  }
})

方式二:按钮分享

xml 复制代码
<!-- wxml -->
<button open-type="share">
  分享给好友
</button>
javascript 复制代码
// js
Page({
  onShareAppMessage({ from, target }) {
    if (from === 'button') {
      // 按钮分享
      return {
        title: '按钮分享标题',
        path: '/pages/index/index?shareFrom=button'
      }
    }
    // 右上角菜单分享
    return {
      title: '默认分享标题',
      path: '/pages/index/index'
    }
  }
})

方式三:分享到朋友圈

javascript 复制代码
Page({
  onShareTimeline() {
    return {
      title: '朋友圈分享标题',
      query: 'key1=value1&key2=value2',
      imageUrl: 'https://example.com/timeline.png'
    }
  }
})

分享参数说明

参数 类型 必填 说明
title String 分享标题,默认小程序名称
path String 分享路径,默认当前页面
imageUrl String 分享图片,默认截图
query String 查询参数(朋友圈)

分享回调

javascript 复制代码
Page({
  onShareAppMessage() {
    return {
      title: '分享标题',
      path: '/pages/index/index',
      success(res) {
        console.log('分享成功', res)
        // 分享成功统计
      },
      fail(err) {
        console.log('分享失败', err)
      }
    }
  }
})

最佳实践

  • 分享路径携带邀请人 ID,用于统计和奖励
  • 自定义分享图片尺寸 5:4,建议 500×400
  • 分享标题要有吸引力,提高点击率
  • 分享页面需要支持分享参数,处理来源识别

常见误区

  • ❌ 可以监听分享是否被点击 → ✅ 只能监听分享动作,无法监听是否被查看
  • ❌ 分享到朋友圈需要额外权限 → ✅ 基础库 2.11.3 以上支持
  • ❌ 分享可以分享任意图片 → ✅ 图片域名需在业务域名中

8. 微信小程序订阅消息

定义

订阅消息是小程序向用户发送通知的能力,取代了原有的模板消息。需要用户主动订阅才能发送,分为一次性订阅和长期订阅。

订阅消息类型

类型 说明 场景
一次性订阅 用户订阅一次可发送一条消息 通知类场景(订单发货、预约提醒)
长期订阅 用户订阅后可长期发送 政务民生、医疗、交通等特定行业

实现流程

步骤一:配置模板

  1. 登录微信公众平台
  2. 进入"功能" → "订阅消息"
  3. 选择合适的模板并添加

步骤二:请求授权

javascript 复制代码
// 请求订阅消息授权
async function requestSubscribe() {
  const res = await wx.requestSubscribeMessage({
    tmplIds: ['TEMPLATE_ID_1', 'TEMPLATE_ID_2']
  })
  
  console.log(res)
  // { TEMPLATE_ID_1: 'accept', TEMPLATE_ID_2: 'reject' }
}

步骤三:发送消息(后端)

javascript 复制代码
// Node.js 后端发送订阅消息
async function sendSubscribeMessage(openid, templateId, data) {
  const accessToken = await getAccessToken()
  
  const url = `https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=${accessToken}`
  
  const body = {
    touser: openid,
    template_id: templateId,
    page: 'pages/order/detail?id=123',
    data: {
      thing1: { value: '订单已发货' },
      time2: { value: '2024-01-15 14:30' },
      phrase3: { value: '已完成' }
    }
  }
  
  const res = await httpsPost(url, body)
  return res
}

完整示例

javascript 复制代码
// 小程序端
Page({
  data: {
    orderStatus: 'pending'
  },
  
  // 用户下单后请求订阅
  async onOrderSuccess() {
    try {
      await wx.requestSubscribeMessage({
        tmplIds: ['order_status_tmpl']
      })
    } catch (e) {
      console.log('用户拒绝订阅')
    }
  }
})

// 后端触发发送
async function notifyOrderStatus(openid, order) {
  await sendSubscribeMessage(openid, 'order_status_tmpl', {
    thing1: { value: `订单${order.id}状态更新` },
    time2: { value: formatTime(new Date()) },
    phrase3: { value: order.status }
  })
}

最佳实践

  • 在合适的时机请求订阅(用户操作后)
  • 不要频繁请求授权
  • 处理用户拒绝的情况
  • 消息内容要与模板匹配

常见误区

  • ❌ 可以无条件发送消息 → ✅ 必须用户订阅
  • ❌ 一次性订阅可以多次发送 → ✅ 订阅一次只能发一次
  • ❌ 前端可以发送订阅消息 → ✅ 必须通过后端 API

9. 微信小程序客服功能

定义

小程序客服是为用户提供在线咨询的能力,用户可以在小程序内直接与商家沟通。微信提供原生客服组件和客服消息两种能力。

实现方式

方式一:原生客服按钮

xml 复制代码
<!-- 使用 button 组件的 open-type -->
<button open-type="contact">
  联系客服
</button>

方式二:自定义客服入口

xml 复制代码
<contact-button 
  size="20" 
  session-from="page-index"
></contact-button>

客服消息处理

javascript 复制代码
// 服务端接收客服消息
app.post('/wechat/callback', (req, res) => {
  const { body } = req
  
  // 解析 XML
  const message = parseXML(body)
  
  if (message.MsgType === 'text') {
    const content = message.Content
    
    // 自动回复
    const reply = generateReply(content)
    
    res.xml(buildReplyXML(message.FromUserName, message.ToUserName, reply))
  }
})

最佳实践

  • 设置自动回复提升响应速度
  • 配置客服工作时间
  • 多客服人员合理分配
  • 重要问题转人工处理

10. 微信小程序广告

定义

小程序广告是微信提供的流量变现能力,开发者可以在小程序中接入广告获取收益。

广告组件类型

类型 说明 使用场景
Banner 广告 横幅广告 页面底部或中部
激励式视频广告 观看视频获取奖励 游戏复活、积分兑换
插屏广告 弹窗广告 页面切换时
格子广告 多广告位 广告专门页面
视频广告 视频流广告 内容流中
原生模板广告 自定义样式 与内容融合

使用示例

xml 复制代码
<!-- Banner 广告 -->
<ad 
  unit-id="adunit-xxx" 
  ad-intervals="30"
  bindload="onAdLoad"
  binderror="onAdError"
  bindclose="onAdClose"
></ad>
javascript 复制代码
// 激励式视频广告
Page({
  onLoad() {
    // 创建激励视频广告实例
    this.videoAd = wx.createRewardedVideoAd({
      adUnitId: 'adunit-xxx'
    })
    
    this.videoAd.onClose((status) => {
      if (status && status.isEnded || status === undefined) {
        // 播放完成,发放奖励
        this.giveReward()
      } else {
        console.log('提前关闭')
      }
    })
  },
  
  // 显示广告
  showVideoAd() {
    this.videoAd.show().catch(() => {
      // 广告拉取失败
      this.videoAd.load()
        .then(() => this.videoAd.show())
        .catch(err => console.error(err))
    })
  },
  
  giveReward() {
    console.log('发放奖励')
  }
})

最佳实践

  • 广告不应影响用户体验
  • 激励广告奖励要及时发放
  • 合理控制广告频率
  • 遵守广告规范

二、支付宝小程序

11. 支付宝小程序是什么?

定义

支付宝小程序是支付宝开放平台提供的轻量级应用,用户可以在支付宝客户端内直接使用,无需下载安装。

与微信小程序对比

对比维度 微信小程序 支付宝小程序
文件后缀 .wxml / .wxss .axml / .acss
开发工具 微信开发者工具 支付宝开发者工具 / IDE
API 前缀 wx. my.
组件名 view / text view / text
数据绑定 {{ }} {{ }}
列表渲染 wx:for a:for
条件渲染 wx:if a:if
事件绑定 bindtap onTap
AppID wx 开头 数字
审核时间 1-7天 1-3天
流量入口 微信内分享、搜索、扫码 支付宝首页、搜索、扫码

开发流程

  1. 注册开放平台账号
  2. 创建小程序获取 AppID
  3. 下载开发者工具
  4. 开发调试
  5. 提交审核
  6. 发布上线

示例

javascript 复制代码
// app.js
App({
  onLaunch(options) {
    console.log('小程序启动', options)
  },
  globalData: {
    userInfo: null
  }
})

// pages/index/index.js
Page({
  data: {
    message: 'Hello Alipay'
  },
  onLoad() {
    my.showToast({ content: '页面加载' })
  }
})
xml 复制代码
<!-- pages/index/index.axml -->
<view class="container">
  <text>{{message}}</text>
  <button onTap="handleTap">点击</button>
</view>

12. 支付宝小程序配置

定义

支付宝小程序的配置结构与微信类似,包含全局配置和页面配置。

app.json 配置

json 复制代码
{
  "pages": [
    "pages/index/index",
    "pages/user/index"
  ],
  "window": {
    "defaultTitle": "支付宝小程序",
    "titleBarColor": "#1677FF",
    "allowsBounceVertical": "YES",
    "transparentTitle": "auto"
  },
  "tabBar": {
    "textColor": "#999",
    "selectedColor": "#1677FF",
    "backgroundColor": "#fff",
    "items": [
      {
        "pagePath": "pages/index/index",
        "name": "首页",
        "icon": "images/home.png",
        "activeIcon": "images/home-active.png"
      },
      {
        "pagePath": "pages/user/index",
        "name": "我的",
        "icon": "images/user.png",
        "activeIcon": "images/user-active.png"
      }
    ]
  }
}

13. 支付宝小程序登录

定义

支付宝小程序登录基于支付宝授权,通过 my.getAuthCode 获取授权码,换取用户标识。

登录流程

javascript 复制代码
// 小程序端
Page({
  async login() {
    // 1. 获取授权码
    const { authCode } = await my.getAuthCode({
      scopes: 'auth_user'
    })
    
    // 2. 发送到后端
    const res = await my.httpRequest({
      url: 'https://api.example.com/alipay/login',
      method: 'POST',
      data: { authCode }
    })
    
    // 3. 存储登录态
    my.setStorageSync({
      key: 'token',
      data: res.data.token
    })
  }
})

后端实现

javascript 复制代码
// 后端换取用户信息
async function alipayLogin(authCode) {
  // 调用支付宝接口
  const result = await alipayClient.exec(
    'alipay.system.oauth.token',
    {
      grant_type: 'authorization_code',
      code: authCode
    }
  )
  
  const { access_token, user_id, alipay_user_id } = result
  
  // 获取用户信息
  const userInfo = await alipayClient.exec(
    'alipay.user.info.share',
    {},
    { authToken: access_token }
  )
  
  // 生成 token
  const token = generateToken(user_id)
  
  return { token, userInfo }
}

14. 支付宝小程序支付

定义

支付宝小程序支付通过 my.tradePay 接口调起收银台,完成支付流程。

实现代码

javascript 复制代码
// 小程序端
Page({
  async doPayment(orderId) {
    // 1. 请求后端获取交易号
    const { data } = await my.httpRequest({
      url: 'https://api.example.com/pay/create',
      method: 'POST',
      data: { orderId }
    })
    
    // 2. 调起支付
    my.tradePay({
      tradeNO: data.tradeNo,
      success: (res) => {
        console.log('支付成功', res)
      },
      fail: (err) => {
        console.error('支付失败', err)
      }
    })
  }
})

后端创建订单

javascript 复制代码
// Node.js 后端
async function createAlipayOrder(params) {
  const { outTradeNo, totalAmount, subject } = params
  
  const result = await alipayClient.exec(
    'alipay.trade.create',
    {
      out_trade_no: outTradeNo,
      total_amount: totalAmount,
      subject: subject
    }
  )
  
  return { tradeNo: result.trade_no }
}

15. 支付宝小程序与微信小程序的区别

对比表格

对比项 微信小程序 支付宝小程序
定位 社交+服务 商业+服务
文件扩展名 wxml/wxss axml/acss
API 前缀 wx. my.
事件绑定 bindtap / catchtap onTap / catchTap
条件渲染 wx:if a:if
列表渲染 wx:for a:for
双向绑定 model:value a:model
组件引用 usingComponents usingComponents
支付 wx.requestPayment my.tradePay
登录 wx.login → code my.getAuthCode → authCode
分享 onShareAppMessage onShareAppMessage
订阅消息 订阅消息 模板消息
用户信息 getUserProfile my.getOpenUserInfo
云开发 微信云开发 支付宝云开发
审核 较严格 相对宽松
流量入口 群聊、朋友圈 支付宝首页、生活号
商业化 广告、电商 金融、商业服务

选择策略

  • 社交属性强 → 微信小程序
  • 商业/金融属性 → 支付宝小程序
  • 覆盖更多用户 → 同时开发或使用跨端框架

三、多端小程序适配

16. 什么是多端小程序?

定义

多端小程序是指一套代码可以编译运行到多个小程序平台(微信、支付宝、百度、头条、QQ等)的技术方案。

跨端框架对比

框架 出品方 语法基础 支持平台 特点
uni-app DCloud Vue 微信/支付宝/百度/头条/H5/App 生态完善,社区活跃
Taro 京东 React/Vue 微信/支付宝/百度/头条/H5/RN 灵活,React生态
mpvue 美团 Vue 微信/头条 已停止维护
Chameleon 滴滴 自研 微信/支付宝/百度 统一多端规范
Remax 阿里 React 微信/支付宝/头条 使用完整React

17. uni-app 详解

定义

uni-app 是基于 Vue.js 的跨端框架,一套代码可编译到 iOS、Android、H5、以及各种小程序。

项目结构

csharp 复制代码
uni-app-project/
├── pages.json          # 页面路由配置
├── manifest.json       # 应用配置
├── App.vue             # 应用入口
├── main.js             # 入口文件
├── pages/              # 页面目录
│   └── index/
│       └── index.vue
├── components/         # 组件目录
├── static/             # 静态资源
├── store/              # Vuex
├── utils/              # 工具函数
└── uni.scss            # 全局样式

条件编译

html 复制代码
<template>
  <view>
    <!-- #ifdef H5 -->
    <text>这是 H5 平台</text>
    <!-- #endif -->
    
    <!-- #ifdef MP-WEIXIN -->
    <text>这是微信小程序</text>
    <!-- #endif -->
    
    <!-- #ifdef APP-PLUS -->
    <text>这是 App</text>
    <!-- #endif -->
  </view>
</template>

<script>
export default {
  onLoad() {
    // #ifdef MP-WEIXIN
    wx.login()
    // #endif
    
    // #ifdef MP-ALIPAY
    my.getAuthCode()
    // #endif
  }
}
</script>

API 统一调用

javascript 复制代码
// uni-app 统一 API
uni.login({
  success(res) {
    console.log(res.code)
  }
})

uni.request({
  url: 'https://api.example.com/data',
  success(res) {
    console.log(res.data)
  }
})

18. Taro 详解

定义

Taro 是京东出品的跨端框架,支持使用 React/Vue/Nerv 等框架开发多端应用。

项目结构

perl 复制代码
taro-project/
├── config/               # 编译配置
│   ├── dev.ts
│   ├── index.ts
│   └── prod.ts
├── src/
│   ├── app.ts            # 入口文件
│   ├── app.config.ts     # 全局配置
│   ├── pages/            # 页面
│   │   └── index/
│   │       ├── index.tsx
│   │       └── index.config.ts
│   ├── components/       # 组件
│   └── store/            # 状态管理
└── package.json

React 示例

tsx 复制代码
// src/pages/index/index.tsx
import { View, Text } from '@tarojs/components'
import { useState } from 'react'
import Taro from '@tarojs/taro'

export default function Index() {
  const [count, setCount] = useState(0)
  
  return (
    <View className="container">
      <Text>计数: {count}</Text>
      <View onClick={() => setCount(c => c + 1)}>
        +1
      </View>
    </View>
  )
}

条件编译

typescript 复制代码
// 条件编译
if (process.env.TARO_ENV === 'weapp') {
  // 微信小程序特有代码
}

if (process.env.TARO_ENV === 'alipay') {
  // 支付宝小程序特有代码
}

19. uni-app 与 Taro 的区别

对比表格

对比项 uni-app Taro
技术栈 Vue 2/3 React/Vue/Nerv
开发公司 DCloud 京东
编译原理 Vue 编译到各端 React/Vue 编译到各端
组件库 uView/uni-ui Taro UI/NutUI
调试体验 HBuilderX 内置 各端开发者工具
生态 插件市场丰富 React 生态
学习成本 Vue 开发者友好 React 开发者友好
性能 较好 较好
社区活跃度 非常活跃 活跃
企业采用 中小企业多 大厂项目多

选择策略

  • 团队熟悉 Vue → uni-app
  • 团队熟悉 React → Taro
  • 需要快速开发 → uni-app(配套工具完善)
  • 需要定制性强 → Taro(灵活度高)

20. 小程序适配方案

实现步骤

方案一:条件编译

javascript 复制代码
// 平台判断
const platform = Taro.getEnv() // WEAPP / ALIPAY / H5

if (platform === 'WEAPP') {
  // 微信小程序
  Taro.login()
} else if (platform === 'ALIPAY') {
  // 支付宝小程序
  Taro.getAuthCode()
}

方案二:统一封装

typescript 复制代码
// utils/auth.ts
interface AuthAdapter {
  login(): Promise<{ code: string }>
  getUserInfo(): Promise<any>
}

class WechatAuth implements AuthAdapter {
  async login() {
    return Taro.login()
  }
  async getUserInfo() {
    return Taro.getUserProfile({ desc: '用户信息' })
  }
}

class AlipayAuth implements AuthAdapter {
  async login() {
    return Taro.getAuthCode({ scopes: 'auth_user' })
  }
  async getUserInfo() {
    return Taro.getOpenUserInfo()
  }
}

// 工厂函数
function createAuthAdapter(): AuthAdapter {
  const env = Taro.getEnv()
  if (env === 'WEAPP') return new WechatAuth()
  if (env === 'ALIPAY') return new AlipayAuth()
  throw new Error('不支持的平台')
}

方案三:平台差异处理

javascript 复制代码
// 支付适配
async function pay(params) {
  const env = Taro.getEnv()
  
  if (env === 'WEAPP') {
    return Taro.requestPayment({
      ...params,
      provider: 'wxpay'
    })
  }
  
  if (env === 'ALIPAY') {
    return Taro.tradePay({
      tradeNO: params.tradeNo
    })
  }
}

21. 条件编译详解

定义

条件编译是在编译时根据目标平台选择性地编译代码,是跨端框架处理平台差异的核心机制。

uni-app 条件编译语法

html 复制代码
<template>
  <view>
    <!-- #ifdef MP-WEIXIN -->
    <button open-type="share">微信分享</button>
    <!-- #endif -->
    
    <!-- #ifdef MP-ALIPAY -->
    <button open-type="share">支付宝分享</button>
    <!-- #endif -->
    
    <!-- #ifdef H5 -->
    <button @click="shareOnH5">H5分享</button>
    <!-- #endif -->
  </view>
</template>

<script>
export default {
  methods: {
    handlePay() {
      // #ifdef MP-WEIXIN
      wx.requestPayment({ ... })
      // #endif
      
      // #ifdef MP-ALIPAY
      my.tradePay({ ... })
      // #endif
      
      // #ifdef H5
      // H5支付逻辑
      // #endif
    }
  }
}
</script>

<style>
/* #ifdef MP-WEIXIN */
.container { padding: 20rpx; }
/* #endif */

/* #ifdef H5 */
.container { padding: 20px; }
/* #endif */
</style>

22. 平台差异处理

常见差异点及处理

1. 单位差异

javascript 复制代码
// 微信使用 rpx,支付宝使用 rpx,H5 使用 px
// uni-app 自动转换
// Taro 需要手动处理

2. 事件参数差异

javascript 复制代码
// 微信
handleTap(e) {
  const value = e.currentTarget.dataset.value
}

// 支付宝
handleTap(e) {
  const value = e.target.dataset.value
}

// 跨端处理
getDataset(e) {
  const env = Taro.getEnv()
  if (env === 'WEAPP') {
    return e.currentTarget.dataset
  }
  return e.target.dataset
}

3. 导航栏差异

json 复制代码
// 微信
{
  "navigationBarTitleText": "标题"
}

// 支付宝
{
  "defaultTitle": "标题"
}

// 跨端配置(uni-app)
{
  "pages": [{
    "path": "pages/index/index",
    "style": {
      "navigationBarTitleText": "标题"
    }
  }]
}

四、小程序云开发

23. 什么是云开发?

定义

小程序云开发是微信提供的 Serverless 服务,开发者无需搭建服务器即可使用云端能力,包括云函数、云数据库、云存储等。

核心能力

javascript 复制代码
┌──────────────────────────────────────────────────────────┐
│                      小程序云开发                          │
├────────────────┬──────────────────┬───────────────────────┤
│   云函数        │    云数据库       │      云存储           │
│  - 服务端逻辑   │  - JSON 文档存储  │  - 文件上传下载       │
│  - 定时触发     │  - 实时推送      │  - 文件管理           │
│  - HTTP 访问   │  - 权限控制      │  - CDN 加速           │
└────────────────┴──────────────────┴───────────────────────┘
                          │
                  ┌───────┴───────┐
                  │   云调用       │
                  │  - 免鉴权调用  │
                  │  - 开放 API    │
                  └───────────────┘

开通云开发

  1. 打开微信开发者工具
  2. 点击"云开发"按钮
  3. 开通并创建环境
  4. 获取环境 ID
javascript 复制代码
// app.js
App({
  onLaunch() {
    wx.cloud.init({
      env: 'your-env-id',
      traceUser: true
    })
  }
})

24. 云函数

定义

云函数是运行在云端的 Node.js 代码,用于处理服务端逻辑。

创建云函数

javascript 复制代码
// cloud-functions/login/index.js
const cloud = require('wx-server-sdk')
cloud.init()

exports.main = async (event, context) => {
  const { userInfo } = event
  const { OPENID } = cloud.getWXContext()
  
  // 保存用户信息
  const db = cloud.database()
  await db.collection('users').add({
    data: {
      openid: OPENID,
      ...userInfo,
      createTime: db.serverDate()
    }
  })
  
  return { openid: OPENID }
}

调用云函数

javascript 复制代码
// 小程序端
wx.cloud.callFunction({
  name: 'login',
  data: { userInfo: { nickName: 'test' } },
  success: res => {
    console.log('云函数返回', res.result)
  }
})

定时触发器

json 复制代码
// cloud-functions/cleanData/config.json
{
  "triggers": [
    {
      "name": "dailyClean",
      "type": "timer",
      "config": "0 0 2 * * * *"
    }
  ]
}
javascript 复制代码
// 定时执行的云函数
exports.main = async (event, context) => {
  const db = cloud.database()
  
  // 清理7天前的数据
  const sevenDaysAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
  
  await db.collection('logs').where({
    createTime: db.command.lt(sevenDaysAgo)
  }).remove()
  
  return { success: true }
}

HTTP 访问

javascript 复制代码
// 开通 HTTP 访问后
// 可以通过 URL 直接调用
// https://env-id.service.tcloudbase.com/login

最佳实践

  • 敏感操作放在云函数
  • 使用云函数免鉴权调用微信开放 API
  • 合理设置超时时间
  • 错误处理要完善

25. 云数据库

定义

云数据库是 JSON 文档型数据库,基于 MongoDB,提供完整的 CRUD 能力。

基本操作

javascript 复制代码
const db = wx.cloud.database()
const _ = db.command

// 增
async function addData() {
  const res = await db.collection('todos').add({
    data: {
      description: '学习云开发',
      done: false,
      createTime: db.serverDate()
    }
  })
  return res._id
}

// 删
async function deleteData(id) {
  await db.collection('todos').doc(id).remove()
}

// 改
async function updateData(id) {
  await db.collection('todos').doc(id).update({
    data: {
      done: true
    }
  })
}

// 查
async function queryData() {
  const res = await db.collection('todos')
    .where({ done: false })
    .orderBy('createTime', 'desc')
    .limit(20)
    .get()
  return res.data
}

高级查询

javascript 复制代码
// 条件查询
const res = await db.collection('todos').where({
  done: false,
  tags: _.in(['work', 'important'])
}).get()

// 范围查询
const res2 = await db.collection('orders').where({
  amount: _.gte(100).lte(500)
}).get()

// 模糊查询(正则)
const res3 = await db.collection('users').where({
  name: db.RegExp({
    regexp: '.*test.*',
    options: 'i'
  })
}).get()

// 聚合查询
const $ = db.command.aggregate
const res4 = await db.collection('orders').aggregate()
  .group({
    _id: '$status',
    count: $.sum(1),
    total: $.sum('$amount')
  })
  .end()

权限管理

权限 说明
仅创建者可读写 只有数据创建者可以操作
仅管理端可写,所有用户可读 适合公告类数据
仅管理端可读写 后台配置数据
所有用户可读 公开数据
所有用户可写 不推荐使用

实时数据推送

javascript 复制代码
// 监听数据变化
const watcher = db.collection('todos').watch({
  onChange(snapshot) {
    console.log('数据变化', snapshot)
  },
  onError(err) {
    console.error('监听失败', err)
  }
})

// 关闭监听
watcher.close()

26. 云存储

定义

云存储提供文件上传下载能力,支持 CDN 加速。

文件上传

javascript 复制代码
// 选择并上传图片
wx.chooseImage({
  count: 1,
  success(res) {
    const filePath = res.tempFilePaths[0]
    const cloudPath = `images/${Date.now()}.jpg`
    
    wx.cloud.uploadFile({
      cloudPath,
      filePath,
      success(res) {
        console.log('上传成功', res.fileID)
      }
    })
  }
})

文件下载

javascript 复制代码
// 下载文件
wx.cloud.downloadFile({
  fileID: 'cloud://xxx.jpg',
  success(res) {
    console.log('下载成功', res.tempFilePath)
  }
})

获取临时链接

javascript 复制代码
// 获取临时 URL(有效期有限)
wx.cloud.getTempFileURL({
  fileList: ['cloud://xxx.jpg'],
  success(res) {
    console.log('临时链接', res.fileList[0].tempFileURL)
  }
})

删除文件

javascript 复制代码
wx.cloud.deleteFile({
  fileList: ['cloud://xxx.jpg'],
  success(res) {
    console.log('删除成功')
  }
})

27. 云调用

定义

云调用是在云函数中免鉴权调用微信开放 API 的能力。

示例

javascript 复制代码
// 云函数中发送订阅消息
const cloud = require('wx-server-sdk')
cloud.init()

exports.main = async (event, context) => {
  const { OPENID } = cloud.getWXContext()
  
  try {
    await cloud.openapi.subscribeMessage.send({
      touser: OPENID,
      page: 'pages/index/index',
      data: {
        thing1: { value: '订单已发货' },
        time2: { value: '2024-01-15' }
      },
      templateId: 'template_id_xxx'
    })
    
    return { success: true }
  } catch (err) {
    return { success: false, error: err }
  }
}

支持的开放 API

  • 订阅消息
  • 统一服务消息
  • 获取小程序码
  • 内容安全检测
  • 物流助手
  • 附近的小程序

28. 云开发与传统后端的区别

对比表格

对比项 云开发 传统后端
服务器 无需搭建 需购买/配置
域名 无需备案 需备案
HTTPS 自动支持 需配置证书
数据库 JSON 文档型 MySQL/MongoDB 等
部署 一键上传 CI/CD 流程
鉴权 免鉴权调用微信 API 需配置 access_token
扩展性 自动扩容 手动扩展
成本 按量付费 固定 + 运维成本
调试 本地+云端 本地+服务器
适合场景 中小型项目 大型复杂项目

选择策略

  • 个人项目/小型项目 → 云开发
  • 大型企业项目 → 传统后端
  • 需要快速验证 → 云开发
  • 需要高度定制 → 传统后端

29. 云开发优势

  1. 快速开发:无需搭建服务器,开箱即用
  2. 免运维:自动扩容,无需关心服务器状态
  3. 天然鉴权:云函数调用微信 API 免 access_token
  4. 成本低:按量付费,空闲不收费
  5. 数据安全:微信提供安全保障
  6. 实时推送:数据库支持实时监听

30. 云开发限制

  1. 冷启动延迟:云函数空闲后重新调用有延迟
  2. 数据库限制:单次查询最多 100 条
  3. 调用频率:云函数并发数有限制
  4. 存储空间:免费额度有限
  5. 绑定环境:一个小程序最多绑定两个环境
  6. 不支持长连接:无法使用 WebSocket

五、小程序性能优化

31. 小程序性能优化概述

优化维度

复制代码
┌──────────────────────────────────────────────────────────┐
│                    小程序性能优化                          │
├──────────┬───────────┬────────────┬───────────┬──────────┤
│ 启动优化  │ 渲染优化   │ 数据优化   │ 包体积优化 │ 网络优化 │
├──────────┼───────────┼────────────┼───────────┼──────────┤
│ 分包加载  │ setData优化│ 数据缓存   │ 代码压缩  │ 请求合并 │
│ 按需注入  │ 列表优化   │ 数据分页   │ 图片压缩  │ 数据预取 │
│ 预下载   │ 避免重排   │ 避免冗余   │ 按需加载  │ CDN加速 │
│ 首屏优化  │ 组件复用   │ 及时清理   │ 移除冗余  │ 弱网处理 │
└──────────┴───────────┴────────────┴───────────┴──────────┘

性能指标

  • 首屏时间 < 1.5 秒
  • setData 调用频率 < 20 次/秒
  • 数据包大小 < 256KB
  • 页面节点数 < 1000

32. setData 优化

定义

setData 是小程序数据绑定的核心 API,频繁或大量使用会导致性能问题。

性能问题根源

  • 逻辑层到渲染层的通信有开销
  • 数据需要 JSON 序列化
  • 频繁调用会阻塞渲染

优化策略

策略一:减少 setData 频率

javascript 复制代码
// ❌ 错误做法:频繁 setData
for (let i = 0; i < 100; i++) {
  this.setData({ count: i })
}

// ✅ 正确做法:批量更新
let count = 0
for (let i = 0; i < 100; i++) {
  count = i
}
this.setData({ count })

策略二:减少数据量

javascript 复制代码
// ❌ 错误做法:传递完整数据
this.setData({
  list: largeArray,        // 1000条数据
  detail: largeObject      // 大对象
})

// ✅ 正确做法:按需传递
this.setData({
  'list[0].name': '新名称',  // 局部更新
  'detail.field': '新值'
})

策略三:避免后台页面 setData

javascript 复制代码
Page({
  onUnload() {
    // 页面销毁时取消定时器
    clearInterval(this.timer)
  },
  
  onHide() {
    // 页面隐藏时停止数据更新
    this.isPageVisible = false
  },
  
  onShow() {
    this.isPageVisible = true
  },
  
  async loadData() {
    const data = await fetchData()
    if (this.isPageVisible !== false) {
      this.setData({ data })
    }
  }
})

策略四:使用局部路径更新

javascript 复制代码
// 更新数组某一项
this.setData({
  'list[0].title': '新标题'
})

// 更新对象属性
this.setData({
  'user.name': '新名称'
})

// 动态路径更新
const index = 0
const field = 'title'
this.setData({
  [`list[${index}].${field}`]: '新值'
})

策略五:数据分离

javascript 复制代码
// 将不需要渲染的数据放在 data 外
Page({
  data: {
    displayList: []  // 只存储需要展示的数据
  },
  rawData: [],        // 存储完整数据
  
  processList(raw) {
    this.rawData = raw
    this.setData({
      displayList: raw.slice(0, 20)  // 只显示前20条
    })
  }
})

33. 分包加载

定义

分包加载是将小程序代码包拆分成多个包,按需加载,减少首屏加载时间。

配置示例

json 复制代码
{
  "pages": [
    "pages/index/index",
    "pages/detail/index"
  ],
  "subPackages": [
    {
      "root": "packageA",
      "pages": [
        "pages/user/index",
        "pages/order/index"
      ]
    },
    {
      "root": "packageB",
      "pages": [
        "pages/shop/index",
        "pages/cart/index"
      ]
    }
  ]
}

目录结构

perl 复制代码
miniprogram/
├── pages/              # 主包页面
│   ├── index/
│   └── detail/
├── packageA/           # 分包A
│   └── pages/
│       ├── user/
│       └── order/
├── packageB/           # 分包B
│   └── pages/
│       ├── shop/
│       └── cart/
└── app.json

分包规则

  • 主包大小 ≤ 2MB
  • 分包大小 ≤ 2MB
  • 总包大小 ≤ 20MB
  • 主包必须包含 tabBar 页面

导航到分包页面

javascript 复制代码
// 跳转到分包页面
wx.navigateTo({
  url: '/packageA/pages/user/index'
})

34. 独立分包

定义

独立分包是可以独立运行的分包,不依赖主包,可以进一步提升启动速度。

配置

json 复制代码
{
  "subPackages": [
    {
      "root": "packageIndependent",
      "independent": true,
      "pages": [
        "pages/activity/index"
      ]
    }
  ]
}

使用场景

  • 营销活动页面
  • 不需要登录的页面
  • 需要快速打开的页面

限制

  • 无法使用主包的自定义组件
  • 无法访问主包的全局数据
  • 需要独立包含所需资源

35. 分包预下载

定义

在用户访问分包页面前提前下载分包,减少跳转等待时间。

配置

json 复制代码
{
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["packageA", "packageB"]
    },
    "pages/detail/index": {
      "packages": ["packageA"]
    }
  }
}

预下载策略

  • 在首页预下载常用分包
  • 根据用户行为预测预下载
  • WiFi 环境下预下载更多

代码预下载

javascript 复制代码
// 主动触发预下载
wx.loadSubPackage({
  name: 'packageA',
  success() {
    console.log('分包加载完成')
  }
})

36. 按需注入

定义

按需注入是让小程序在启动时只注入当前页面所需的自定义组件,减少启动时间和内存占用。

配置

json 复制代码
{
  "lazyCodeLoading": "requiredComponents"
}

效果

  • 减少启动时间
  • 减少内存占用
  • 按需加载组件代码

37. 图片优化

优化策略

策略一:使用合适的格式

xml 复制代码
<!-- 使用 webp 格式(体积更小) -->
<image src="/images/banner.webp" />

<!-- 使用 mode 控制缩放 -->
<image src="/images/photo.jpg" mode="aspectFill" />

策略二:使用 CDN 图片

javascript 复制代码
// 根据设备像素比加载不同分辨率图片
const systemInfo = wx.getSystemInfoSync()
const pixelRatio = systemInfo.pixelRatio

const imageUrl = `https://cdn.example.com/image_${pixelRatio > 2 ? '@3x' : '@2x'}.jpg`

策略三:懒加载

xml 复制代码
<!-- 图片懒加载 -->
<image src="/images/placeholder.png" 
       data-src="/images/real.jpg"
       lazy-load />

策略四:使用骨架屏

xml 复制代码
<!-- 加载时显示骨架 -->
<view class="skeleton" wx:if="{{loading}}">
  <view class="skeleton-line"></view>
  <view class="skeleton-line"></view>
</view>

<view wx:else>
  <image src="{{imageUrl}}" />
</view>

38. 代码包体积优化

优化策略

策略一:清理无用代码

json 复制代码
// project.config.json
{
  "packOptions": {
    "ignore": [
      {
        "type": "file",
        "value": "test.js"
      },
      {
        "type": "folder",
        "value": "tests"
      }
    ]
  }
}

策略二:使用分包

json 复制代码
{
  "subPackages": [
    {
      "root": "packageA",
      "pages": [...]
    }
  ]
}

策略三:压缩图片

bash 复制代码
# 使用 tinypng 等工具压缩图片
# 优先使用 webp 格式

策略四:按需引入组件库

javascript 复制代码
// ❌ 全量引入
import vant from 'vant-weapp'

// ✅ 按需引入
import { Button, Cell } from 'vant-weapp'

策略五:移除 console

json 复制代码
// project.config.json
{
  "setting": {
    "minified": true,
    "uglifyFileName": true
  }
}

39. 渲染性能优化

优化策略

策略一:减少节点数

xml 复制代码
<!-- ❌ 嵌套过深 -->
<view>
  <view>
    <view>
      <view>
        <text>内容</text>
      </view>
    </view>
  </view>
</view>

<!-- ✅ 扁平化 -->
<view class="container">
  <text>内容</text>
</view>

策略二:避免频繁更新

javascript 复制代码
// ❌ 动画使用 setData
setInterval(() => {
  this.setData({ left: this.data.left + 1 })
}, 16)

// ✅ 使用 CSS 动画或 wx.createAnimation

策略三:列表优化

xml 复制代码
<!-- 使用 wx:key 提升性能 -->
<view wx:for="{{list}}" wx:key="id">
  <text>{{item.name}}</text>
</view>

策略四:使用自定义组件

javascript 复制代码
// 将复杂逻辑封装成组件
// 避免页面过于臃肿
Component({
  properties: {
    data: Object
  },
  methods: {
    // 组件内部逻辑
  }
})

40. 避免频繁 setData

常见场景及优化

场景一:滚动事件

javascript 复制代码
// ❌ 每次滚动都 setData
Page({
  onScroll(e) {
    this.setData({ scrollTop: e.detail.scrollTop })
  }
})

// ✅ 节流处理
Page({
  onScroll(e) {
    this.throttleScroll(e)
  },
  throttleScroll: throttle(function(e) {
    this.setData({ scrollTop: e.detail.scrollTop })
  }, 100)
})

function throttle(fn, delay) {
  let timer = null
  return function(...args) {
    if (!timer) {
      timer = setTimeout(() => {
        fn.apply(this, args)
        timer = null
      }, delay)
    }
  }
}

场景二:实时搜索

javascript 复制代码
// ❌ 每次输入都请求
Page({
  onSearchInput(e) {
    this.search(e.detail.value)
  }
})

// ✅ 防抖处理
Page({
  onSearchInput: debounce(function(e) {
    this.search(e.detail.value)
  }, 300)
})

function debounce(fn, delay) {
  let timer = null
  return function(...args) {
    clearTimeout(timer)
    timer = setTimeout(() => fn.apply(this, args), delay)
  }
}

六、小程序组件库

41. 小程序组件库概述

定义

小程序组件库是预先封装好的 UI 组件集合,帮助开发者快速构建小程序界面。

主流组件库对比

组件库 出品方 特点 适用场景
Vant Weapp 有赞 组件丰富,文档完善 电商类小程序
WeUI 微信官方 原生风格,轻量 基础组件需求
iView Weapp TalkingData 设计精美 管理后台类
ColorUI 个人 色彩丰富 视觉要求高
Taro UI 京东 跨端支持 Taro 项目
uni-ui DCloud 跨端支持 uni-app 项目

42. Vant Weapp

定义

Vant Weapp 是有赞团队开源的小程序 UI 组件库,组件丰富,文档完善。

安装使用

bash 复制代码
# npm 安装
npm i @vant/weapp -S --production

使用示例

json 复制代码
// page.json
{
  "usingComponents": {
    "van-button": "@vant/weapp/button/index",
    "van-cell": "@vant/weapp/cell/index",
    "van-dialog": "@vant/weapp/dialog/index"
  }
}
xml 复制代码
<!-- wxml -->
<van-button type="primary" bind:click="onSubmit">
  提交
</van-button>

<van-cell-group>
  <van-cell title="单元格" value="内容" />
</van-cell-group>

常用组件

  • Button 按钮
  • Cell 单元格
  • Dialog 弹窗
  • Toast 提示
  • Form 表单
  • Tab 标签页
  • Popup 弹出层

43. WeUI

定义

WeUI 是微信官方设计团队为小程序量身定制的 UI 组件库,与微信原生视觉体验一致。

使用方式

json 复制代码
// page.json
{
  "usingComponents": {
    "mp-button": "weui-miniprogram/button/button"
  }
}

特点

  • 官方出品,风格统一
  • 组件精简,轻量级
  • 持续更新维护

44. 自定义组件库

定义

自定义组件是开发者根据业务需求封装的可复用组件。

创建自定义组件

javascript 复制代码
// components/custom-button/index.js
Component({
  properties: {
    text: {
      type: String,
      value: '按钮'
    },
    type: {
      type: String,
      value: 'default'
    },
    disabled: {
      type: Boolean,
      value: false
    }
  },
  
  data: {
    loading: false
  },
  
  methods: {
    onTap() {
      if (this.data.disabled || this.data.loading) return
      
      this.setData({ loading: true })
      this.triggerEvent('click')
    },
    
    reset() {
      this.setData({ loading: false })
    }
  }
})
xml 复制代码
<!-- components/custom-button/index.wxml -->
<view 
  class="custom-button custom-button--{{type}} {{disabled ? 'is-disabled' : ''}}"
  bindtap="onTap"
>
  <text wx:if="{{loading}}">加载中...</text>
  <text wx:else>{{text}}</text>
</view>
wxss 复制代码
/* components/custom-button/index.wxss */
.custom-button {
  padding: 20rpx 40rpx;
  border-radius: 8rpx;
  text-align: center;
}

.custom-button--primary {
  background: #07C160;
  color: #fff;
}

.custom-button--default {
  background: #f7f7f7;
  color: #333;
}

.is-disabled {
  opacity: 0.5;
}

使用自定义组件

json 复制代码
// page.json
{
  "usingComponents": {
    "custom-button": "/components/custom-button/index"
  }
}
xml 复制代码
<!-- wxml -->
<custom-button 
  text="提交订单" 
  type="primary"
  bind:click="handleSubmit"
/>

组件通信

javascript 复制代码
// 父传子:properties
// 子传父:triggerEvent
// 兄弟通信:父组件中转
// 跨层级:provide/inject(基础库 2.9.2+)

// 父组件
Component({
  data: { message: 'hello' }
})

// 子组件
Component({
  properties: { message: String }
})

七、第三方 SDK 集成

45. 第三方 SDK 概述

定义

第三方 SDK 是为了扩展小程序能力而集成的外部开发工具包。

常用 SDK 分类

类别 SDK 用途
支付 微信支付 SDK 小程序内支付
地图 腾讯地图 SDK 定位、路线规划
统计 友盟、TalkingData 数据统计分析
推送 模板消息 SDK 消息推送
分享 分享组件 社交分享
登录 微信开放平台 第三方登录

46. 微信开放平台

定义

微信开放平台提供 APP、网站、公众号与小程序的账号互通能力。

UnionID 机制

javascript 复制代码
// 同一开放平台账号下的小程序、公众号、APP
// 用户可以通过 UnionID 识别

// 获取 UnionID 的条件:
// 1. 用户关注公众号
// 2. 用户曾授权登录
// 3. 通过开放平台绑定

// 云函数中获取
const cloud = require('wx-server-sdk')
exports.main = async (event, context) => {
  const { OPENID, UNIONID } = cloud.getWXContext()
  return { openid: OPENID, unionid: UNIONID }
}

47. 地图 SDK

腾讯地图 SDK 使用

javascript 复制代码
// 引入 SDK
const QQMapWX = require('../../libs/qqmap-wx-jssdk.js')
const qqmapsdk = new QQMapWX({ key: 'YOUR_KEY' })

Page({
  // 获取当前位置
  async getLocation() {
    wx.getLocation({
      type: 'gcj02',
      success: async (res) => {
        const { latitude, longitude } = res
        
        // 逆地址解析
        const result = await qqmapsdk.reverseGeocoder({
          location: { latitude, longitude }
        })
        
        console.log('当前位置:', result.result.address)
      }
    })
  },
  
  // 搜索地点
  async searchPlace(keyword) {
    const result = await qqmapsdk.search({
      keyword,
      location: '39.980017,116.313972'
    })
    
    return result.data
  }
})

48. 统计 SDK

友盟统计集成

javascript 复制代码
// app.js
App({
  onLaunch() {
    // 初始化
    wx.umeng.trackInit({
      appKey: 'YOUR_APP_KEY'
    })
  },
  
  onShow(options) {
    // 页面统计
    wx.umeng.setPageCollection()
  }
})

// 自定义事件
function trackEvent(eventName, params) {
  wx.umeng.trackEvent(eventName, params)
}

// 使用
trackEvent('buy_product', {
  productId: '123',
  price: 99.9
})

49. 分享 SDK

自定义分享实现

javascript 复制代码
// utils/share.js
export function generateShareParams(options) {
  return {
    title: options.title || '默认标题',
    path: `/pages/index/index?shareId=${options.shareId}`,
    imageUrl: options.imageUrl || '/images/share.png',
    success() {
      // 分享成功统计
      wx.umeng.trackEvent('share_success')
    }
  }
}

// 页面中使用
Page({
  onShareAppMessage() {
    return generateShareParams({
      title: '分享标题',
      shareId: 'user123',
      imageUrl: 'https://xxx.png'
    })
  }
})

八、小程序数据分析

50. 小程序统计

定义

小程序统计是对小程序使用情况的数据收集和分析,帮助优化产品体验。

微信小程序数据分析后台

  1. 登录微信公众平台
  2. 进入"统计" → "数据分析"
  3. 查看各项指标

核心指标

  • 访问人数/次数
  • 页面访问路径
  • 停留时长
  • 跳出率
  • 新老用户占比

51. 用户画像

定义

用户画像是对小程序用户属性的分析,包括性别、年龄、地区等。

数据维度

维度 说明
性别分布 男女比例
年龄分布 各年龄段占比
地区分布 各省/市用户分布
设备分布 iOS/Android 比例
终端类型 手机/平板

应用场景

  • 精准营销
  • 内容推荐
  • 产品优化

52. 页面访问分析

定义

页面访问分析是对各页面访问数据的统计,了解用户行为路径。

分析指标

指标 说明
访问次数 页面被打开的次数
访问人数 访问页面的独立用户数
停留时长 用户在页面的平均停留时间
退出率 从该页面退出小程序的比例
分享次数 页面被分享的次数

优化策略

  • 高退出率页面需要优化内容
  • 低停留时长页面需要提升吸引力
  • 高访问页面优先优化性能

53. 自定义分析

定义

自定义分析是开发者根据业务需求自定义的数据分析维度。

实现方式

javascript 复制代码
// 自定义上报
function customAnalysis() {
  // 上报自定义数据
  wx.reportAnalytics('custom_event', {
    action: 'click',
    target: 'button',
    value: 100
  })
}

// 云函数分析
exports.main = async (event, context) => {
  const db = cloud.database()
  
  // 统计分析数据
  const result = await db.collection('analytics').aggregate()
    .group({
      _id: '$action',
      count: $.sum(1)
    })
    .sort({ count: -1 })
    .end()
  
  return result
}

54. 事件上报

定义

事件上报是记录用户特定行为的过程,用于数据分析。

实现代码

javascript 复制代码
// 事件上报封装
class AnalyticsTracker {
  // 页面访问
  static trackPageView(pageName, params = {}) {
    wx.reportAnalytics('page_view', {
      page_name: pageName,
      ...params
    })
  }
  
  // 按钮点击
  static trackClick(buttonName, params = {}) {
    wx.reportAnalytics('button_click', {
      button_name: buttonName,
      ...params
    })
  }
  
  // 自定义事件
  static trackEvent(eventName, params = {}) {
    wx.reportAnalytics(eventName, params)
  }
}

// 使用
AnalyticsTracker.trackPageView('home')
AnalyticsTracker.trackClick('buy_button', { productId: '123' })

55. 埋点

定义

埋点是在代码中特定位置插入数据收集逻辑,记录用户行为。

埋点方案

方案一:代码埋点

javascript 复制代码
// 在关键位置手动埋点
function onBuyClick(productId) {
  // 业务逻辑
  doBuy(productId)
  
  // 埋点
  wx.reportAnalytics('buy_click', {
    product_id: productId,
    timestamp: Date.now()
  })
}

方案二:声明式埋点

xml 复制代码
<!-- 在模板中声明埋点 -->
<button 
  data-track="buy_click"
  data-track-data="{{ {productId: item.id} }}"
  bindtap="onTrackAndExecute"
>
  购买
</button>
javascript 复制代码
// 统一处理
Page({
  onTrackAndExecute(e) {
    const { track, trackData } = e.currentTarget.dataset
    wx.reportAnalytics(track, trackData)
    
    // 执行原有逻辑
    this[track](e)
  }
})

方案三:无埋点(全埋点)

javascript 复制代码
// 拦截页面生命周期
const originalPage = Page
Page = function(config) {
  const originalOnLoad = config.onLoad
  config.onLoad = function(options) {
    // 自动上报页面访问
    wx.reportAnalytics('page_load', {
      page: getCurrentPages().pop().route
    })
    
    if (originalOnLoad) originalOnLoad.call(this, options)
  }
  
  return originalPage(config)
}

最佳实践

  • 埋点要有规划,不要随意添加
  • 关键业务节点必须埋点
  • 埋点数据要验证和清洗
  • 注意用户隐私保护

56. 数据可视化

定义

数据可视化是将分析结果以图表等形式展示。

小程序内图表实现

使用 echarts-for-weixin

xml 复制代码
<!-- wxml -->
<ec-canvas 
  id="mychart-dom-bar" 
  canvas-id="mychart-bar" 
  ec="{{ ec }}"
></ec-canvas>
javascript 复制代码
import * as echarts from '../../ec-canvas/echarts'

function initChart(canvas, width, height, dpr) {
  const chart = echarts.init(canvas, null, {
    width: width,
    height: height,
    devicePixelRatio: dpr
  })
  canvas.setChart(chart)
  
  const option = {
    title: { text: '访问统计' },
    xAxis: {
      type: 'category',
      data: ['周一', '周二', '周三', '周四', '周五', '周六', '周日']
    },
    yAxis: { type: 'value' },
    series: [{
      data: [120, 200, 150, 80, 70, 110, 130],
      type: 'bar'
    }]
  }
  
  chart.setOption(option)
  return chart
}

Page({
  data: {
    ec: { onInit: initChart }
  }
})

九、综合实战

57. 小程序启动流程

详细流程

markdown 复制代码
1. 下载代码包(主包)
   ↓
2. 解压代码包
   ↓
3. 执行 app.js 的 App() 构造
   ↓
4. 触发 onLaunch 生命周期
   ↓
5. 渲染首页
   ↓
6. 触发首页 onLoad → onShow → onReady

启动优化

javascript 复制代码
App({
  async onLaunch() {
    // 并行执行独立任务
    const [loginRes, configRes] = await Promise.all([
      this.doLogin(),
      this.loadConfig()
    ])
    
    // 使用缓存数据
    const cachedData = wx.getStorageSync('homeData')
    if (cachedData) {
      this.globalData.homeData = cachedData
    }
  },
  
  async doLogin() {
    // 登录逻辑
  },
  
  async loadConfig() {
    // 加载配置
  }
})

58. 小程序生命周期

应用生命周期

javascript 复制代码
App({
  onLaunch(options) {
    // 小程序初始化完成(全局只触发一次)
    console.log('启动参数', options)
  },
  
  onShow(options) {
    // 小程序从后台进入前台
    console.log('场景值', options.scene)
  },
  
  onHide() {
    // 小程序从前台进入后台
  },
  
  onError(error) {
    // 脚本错误或 API 调用失败
    console.error('错误', error)
  },
  
  onPageNotFound(res) {
    // 页面不存在时的回调
    wx.redirectTo({ url: '/pages/index/index' })
  }
})

页面生命周期

javascript 复制代码
Page({
  onLoad(options) {
    // 页面加载(只执行一次)
  },
  
  onShow() {
    // 页面显示(每次显示都触发)
  },
  
  onReady() {
    // 页面初次渲染完成
  },
  
  onHide() {
    // 页面隐藏
  },
  
  onUnload() {
    // 页面卸载
  },
  
  onPullDownRefresh() {
    // 下拉刷新
    wx.stopPullDownRefresh()
  },
  
  onReachBottom() {
    // 上拉触底(分页加载)
  },
  
  onShareAppMessage() {
    // 分享
  },
  
  onPageScroll(e) {
    // 页面滚动
  }
})

十、高频面试题

59. 小程序双线程架构的优缺点?

优点

  • 安全性高:逻辑层与渲染层隔离,不能直接操作 DOM
  • 可控性强:微信可以管控脚本执行
  • 体验一致:双线程架构保证跨平台体验一致

缺点

  • 通信开销:逻辑层和渲染层通信需要序列化
  • 无法直接操作 DOM:需要使用 setData
  • 性能损耗:数据传递存在延迟

60. 小程序如何实现长列表优化?

方案一:分页加载

javascript 复制代码
Page({
  data: {
    list: [],
    page: 1,
    hasMore: true
  },
  
  async loadData() {
    const res = await api.getList({ page: this.data.page })
    this.setData({
      list: [...this.data.list, ...res.data],
      page: this.data.page + 1,
      hasMore: res.hasMore
    })
  },
  
  onReachBottom() {
    if (this.data.hasMore) {
      this.loadData()
    }
  }
})

方案二:虚拟列表

xml 复制代码
<!-- 只渲染可视区域的元素 -->
<view style="height: {{totalHeight}}px; position: relative;">
  <view style="position: absolute; top: {{startIndex * itemHeight}}px;">
    <view wx:for="{{visibleList}}" wx:key="id">
      <text>{{item.content}}</text>
    </view>
  </view>
</view>

61. 小程序如何实现扫码功能?

javascript 复制代码
Page({
  async scanCode() {
    try {
      const res = await wx.scanCode({
        onlyFromCamera: false,
        scanType: ['qrCode', 'barCode']
      })
      
      console.log('扫码结果', res.result)
      // 处理扫码结果
      this.handleScanResult(res.result)
    } catch (err) {
      console.log('扫码失败', err)
    }
  }
})

62. 小程序如何实现文件下载?

javascript 复制代码
Page({
  async downloadFile(url) {
    wx.showLoading({ title: '下载中' })
    
    try {
      const res = await wx.downloadFile({ url })
      
      // 保存文件
      const saveRes = await wx.saveFileToDisk({
        filePath: res.tempFilePath
      })
      
      wx.showToast({ title: '下载成功' })
    } catch (err) {
      wx.showToast({ title: '下载失败', icon: 'none' })
    } finally {
      wx.hideLoading()
    }
  }
})

63. 小程序如何实现 WebSocket?

javascript 复制代码
class WebSocketManager {
  constructor(url) {
    this.url = url
    this.isConnected = false
    this.reconnectCount = 0
    this.maxReconnect = 5
  }
  
  connect() {
    wx.connectSocket({ url: this.url })
    
    wx.onSocketOpen(() => {
      this.isConnected = true
      this.reconnectCount = 0
      console.log('WebSocket 已连接')
    })
    
    wx.onSocketMessage((res) => {
      const data = JSON.parse(res.data)
      this.onMessage(data)
    })
    
    wx.onSocketClose(() => {
      this.isConnected = false
      this.reconnect()
    })
    
    wx.onSocketError((err) => {
      console.error('WebSocket 错误', err)
      this.reconnect()
    })
  }
  
  reconnect() {
    if (this.reconnectCount < this.maxReconnect) {
      this.reconnectCount++
      setTimeout(() => this.connect(), 3000)
    }
  }
  
  send(data) {
    if (this.isConnected) {
      wx.sendSocketMessage({
        data: JSON.stringify(data)
      })
    }
  }
  
  close() {
    wx.closeSocket()
  }
  
  onMessage(data) {
    // 子类实现
  }
}
相关推荐
walking9571 小时前
重新学习前端之CSS
前端·vue.js·面试
walking9571 小时前
重新学习前端之Git
前端·vue.js·面试
魔术师Grace1 小时前
AI让我退化成原始人了
前端·程序员
铁皮饭盒1 小时前
今天你会学到这些关键词
前端·后端
李剑一1 小时前
耗时 2 小时!我复刻了全网超火的通透 3D 水晶球动效,Vue3+Three.js 做出高级视觉特效
前端·three.js
oil欧哟1 小时前
🤔 很长时间没写文章了,分享一下最近的一些思考
前端·后端
Hello--_--World2 小时前
Vue指令:v-if vs v-show、v-if 与 v-for 的优先级冲突、自定义指令
前端·javascript·vue.js
神の愛2 小时前
ReactHooks
前端·javascript·react.js
蝎子莱莱爱打怪2 小时前
用好CC,事半功倍!Claude Code 命令大全,黄金命令推荐、多模型配置、实践指南、Hooks 和踩坑记录大全
前端·人工智能·后端