小程序常见问题

小程序开发中的常见问题及解决方案

问题

小程序开发中有哪些常见问题?如何解决?

解答

1. setData 性能问题

小程序采用双线程架构,逻辑层和渲染层通过 setData 通信,频繁或大量数据传输会导致性能问题。

// ❌ 错误:频繁调用 setData
for (let i = 0; i < 100; i++) {
  this.setData({ [`list[${i}]`]: data[i] })
}

// ❌ 错误:传输大量无关数据
this.setData({
  userInfo: this.data.userInfo, // 未变化的数据
  list: newList
})

// ✅ 正确:合并更新,只传必要数据
this.setData({
  list: newList
})

// ✅ 正确:使用路径更新
this.setData({
  'list[0].name': 'new name',
  'userInfo.avatar': 'new url'
})

2. 页面栈限制

小程序页面栈最多 10 层,超出后 navigateTo 会失败。

// 获取当前页面栈
const pages = getCurrentPages()
console.log('当前页面栈深度:', pages.length)

// 页面跳转前检查
function navigateTo(url) {
  const pages = getCurrentPages()
  if (pages.length >= 10) {
    // 使用 redirectTo 替换当前页
    wx.redirectTo({ url })
  } else {
    wx.navigateTo({ url })
  }
}

// 返回多级页面
wx.navigateBack({ delta: 2 })

// 跳转到 tabBar 页面
wx.switchTab({ url: '/pages/home/index' })

// 关闭所有页面,打开新页面
wx.reLaunch({ url: '/pages/index/index' })

3. 跨页面通信

// 方案一:EventBus
const eventBus = {
  events: {},
  on(name, callback) {
    (this.events[name] || (this.events[name] = [])).push(callback)
  },
  emit(name, data) {
    (this.events[name] || []).forEach(cb => cb(data))
  },
  off(name, callback) {
    if (!callback) {
      this.events[name] = []
    } else {
      this.events[name] = (this.events[name] || []).filter(cb => cb !== callback)
    }
  }
}

// 页面 A:监听
eventBus.on('updateList', (data) => {
  this.setData({ list: data })
})

// 页面 B:触发
eventBus.emit('updateList', newList)

// 方案二:获取页面实例直接调用
const pages = getCurrentPages()
const prevPage = pages[pages.length - 2]
prevPage.setData({ needRefresh: true })

// 方案三:全局状态管理
const app = getApp()
app.globalData.userInfo = newUserInfo

4. 分包加载

主包限制 2MB,总包限制 20MB。

// app.json
{
  "pages": [
    "pages/index/index"
  ],
  "subpackages": [
    {
      "root": "packageA",
      "pages": ["pages/detail/index"]
    },
    {
      "root": "packageB",
      "pages": ["pages/list/index"],
      "independent": true  // 独立分包,可独立运行
    }
  ],
  "preloadRule": {
    "pages/index/index": {
      "network": "all",
      "packages": ["packageA"]  // 预加载分包
    }
  }
}

5. 生命周期执行顺序

// App 生命周期
App({
  onLaunch() {},  // 小程序初始化,全局只触发一次
  onShow() {},    // 小程序启动或从后台进入前台
  onHide() {}     // 小程序从前台进入后台
})

// Page 生命周期
Page({
  onLoad() {},    // 页面加载,只调用一次
  onShow() {},    // 页面显示,每次打开都调用
  onReady() {},   // 页面初次渲染完成
  onHide() {},    // 页面隐藏
  onUnload() {}   // 页面卸载
})

// Component 生命周期
Component({
  lifetimes: {
    created() {},   // 组件实例创建
    attached() {},  // 组件进入页面节点树
    ready() {},     // 组件布局完成
    detached() {}   // 组件离开页面节点树
  },
  pageLifetimes: {
    show() {},      // 所在页面显示
    hide() {}       // 所在页面隐藏
  }
})

6. 授权问题处理

// 检查授权状态
wx.getSetting({
  success(res) {
    if (res.authSetting['scope.userLocation']) {
      // 已授权
      wx.getLocation({ type: 'wgs84' })
    } else if (res.authSetting['scope.userLocation'] === false) {
      // 曾拒绝,需引导用户打开设置
      wx.showModal({
        title: '提示',
        content: '需要获取位置权限',
        success(res) {
          if (res.confirm) {
            wx.openSetting()
          }
        }
      })
    } else {
      // 未请求过,发起授权
      wx.authorize({
        scope: 'scope.userLocation',
        success() {
          wx.getLocation({ type: 'wgs84' })
        }
      })
    }
  }
})

// 获取用户信息(需要 button 触发)
// <button open-type="getUserInfo" bindgetuserinfo="onGetUserInfo">授权</button>

7. 请求封装

// utils/request.js
const BASE_URL = 'https://api.example.com'

function request(options) {
  return new Promise((resolve, reject) => {
    wx.showLoading({ title: '加载中' })
    
    wx.request({
      url: BASE_URL + options.url,
      method: options.method || 'GET',
      data: options.data,
      header: {
        'Authorization': wx.getStorageSync('token'),
        ...options.header
      },
      success(res) {
        if (res.statusCode === 200) {
          resolve(res.data)
        } else if (res.statusCode === 401) {
          // token 过期,跳转登录
          wx.navigateTo({ url: '/pages/login/index' })
          reject(res)
        } else {
          wx.showToast({ title: '请求失败', icon: 'none' })
          reject(res)
        }
      },
      fail: reject,
      complete() {
        wx.hideLoading()
      }
    })
  })
}

export const get = (url, data) => request({ url, data, method: 'GET' })
export const post = (url, data) => request({ url, data, method: 'POST' })

关键点

  • setData 优化:减少调用频率,只传必要数据,使用路径更新
  • 页面栈管理:注意 10 层限制,合理使用 redirectTo、reLaunch
  • 分包加载:主包放核心页面,业务模块分包,配置预加载
  • 跨页面通信:EventBus、getCurrentPages、globalData 三种方案
  • 授权处理:区分未授权、已拒绝、已授权三种状态