微信小程序开发学习文档(九)

九、工程化与最佳实践

当项目从个人 Demo 走向团队协作、从单一页面走向复杂业务时,原生开发的简陋体验会逐渐暴露------没有类型检查、没有构建优化、没有统一的代码规范。

本章聚焦小程序的工程化演进路径,帮助项目从"能跑"过渡到"可维护"。

9.1 构建工具与工程化

9.1.1 原生小程序工程化痛点

在开始引入框架前,先理解原生开发在工程化方面的主要短板:

|--------------------|-------------------------------|-------------|
| 痛点 | 具体表现 | 影响 |
| 无预处理语言 | 无法使用 Sass/Less、TypeScript | 样式冗余、缺乏类型检查 |
| 手动分包 | 需手动规划目录,维护 `app.json` 的分包配置 | 体积控制困难 |
| 无 Tree Shaking | 未使用的代码和组件会被打包 | 包体积膨胀 |
| 测试困难 | 官方未提供测试工具链 | 质量保障靠手工回归 |
| 环境变量缺失 | 无内置的 dev/staging/prod 环境切换 | 切换环境靠注释代码 |
| 原子化 CSS 缺失 | 没有类似 Tailwind 的工具 | 样式编写效率低 |

这些痛点并非小程序的缺陷,而是它"轻量、零配置"定位下的取舍。当项目复杂度突破阈值后,引入合适的框架或工具链是必要的。

9.1.2 主流框架对比:uni-app、Taro、mpvue(2026 现状)

|-----------------|--------------------------|-----------------------|----------------|------------|-------------|
| 框架 | 开发方式 | 多端支持 | TypeScript | 社区生态 | 适用场景 |
| uni-app | Vue 3 语法,编译为小程序 | 一套代码多端发布(H5、App、各小程序) | 完善 | 最活跃,插件市场丰富 | 多端同步发布需求 |
| Taro | React/Vue 3 语法 | 一套代码多端发布 | 完善 | 活跃,京东出品 | React 技术栈团队 |
| mpvue | Vue 2 语法 | 仅微信小程序 | 部分支持 | 已停止维护 | 不建议新项目使用 |
| 原生 + 增强 | 原生语法 + TypeScript + Sass | 仅微信小程序 | 需手动配置 | 依赖官方工具链 | 纯小程序项目 |

选择建议:

  • 只需发布微信小程序,且团队规模较小:原生开发足够,维护成本最低。

  • 需要同时发布微信、支付宝、H5 等多端:uni-app 是多端覆盖最全面的选择。

  • 团队使用 React 技术栈且需要多端:Taro 是最佳选择。

  • 无论选择哪个框架,都建议先理解原生小程序的运行原理(本文已覆盖),再学习框架的抽象层。

9.1.3 微信官方工程化工具:WePY 3.0

WePY 是微信团队早期推出的类 Vue 语法的组件化框架。3.0 版本重写后,支持 Vue 3 Composition API、TypeScript、Sass 等现代前端特性。

但由于 uni-app 和 Taro 的强势发展,WePY 如今的使用率和社区活跃度已大幅下降。如果团队无历史包袱,优先考虑 uni-app 或 Taro。

9.1.4 TypeScript 在小程序中的应用

在原生小程序中使用 TypeScript 可以通过安装官方类型包实现:

bash 复制代码
npm install miniprogram-api-typings --save-dev
javascript 复制代码
// tsconfig.json

{

  "compilerOptions": {

    "target": "ES2020",

    "module": "CommonJS",

    "strict": true,

    "esModuleInterop": true,

    "skipLibCheck": true,

    "types": ["miniprogram-api-typings"]

  },

  "include": [".//.ts"],

  "exclude": ["node_modules"]

}
javascript 复制代码
// pages/index/index.ts

/ 定义页面数据类型 /

interface PageData {

  userList: User[]

  loading: boolean

  error: string

}



interface User {

  id: string

  name: string

  avatar: string

}



Page<PageData, {}>({

  data: {

    userList: [],

    loading: false,

    error: ''

  },



  async onLoad() {

    this.setData({ loading: true })

    try {

      const list: User[] = await api.getUserList()

      this.setData({ userList: list, loading: false })

    } catch (err) {

      this.setData({ error: '加载失败', loading: false })

    }

  }

})

TypeScript 在小程序中的价值:最显著的收益不是"少写 Bug",而是在重构和协作时编辑器能提供精准的智能提示和接口说明,大幅降低理解成本。

9.2 代码规范与质量

9.2.1 ESLint + Prettier 代码检查与格式化

bash 复制代码
npm install eslint prettier eslint-config-prettier --save-dev
javascript 复制代码
// .eslintrc.js

module.exports = {

  env: {

    browser: true,

    es2021: true

  },

  globals: {

    wx: 'readonly',

    App: 'readonly',

    Page: 'readonly',

    Component: 'readonly',

    getApp: 'readonly',

    getCurrentPages: 'readonly'

  },

  rules: {

    'no-console': 'warn',       / 警告 console 遗留 /

    'no-unused-vars': 'error',  / 未使用变量报错 /

    'no-var': 'error',          / 禁止 var /

    'prefer-const': 'error'     / 优先 const /

  }

}

关键配置:

在 `globals` 中声明小程序的全局变量,避免 ESLint 误报 `wx is not defined`。

9.2.2 小程序特有的代码规范

|----------------------|------------------------------------------|--------------|
| 规范项 | 建议 | 理由 |
| 页面名规范 | 小写 + 短横线:`pages/order-detail/` | 与文件命名一致 |
| setData 数据键名 | 驼峰:`userName`,非 `user_name` | 与 JS 变量风格统一 |
| 组件引用名 | 短横线 + 前缀:`custom-modal` | 区分内置组件和自定义组件 |
| API 调用 | 统一通过封装的请求层,不直接调 `wx.request` | 便于拦截、重试、埋点 |
| 图片路径 | 使用绝对路径 `/images/icon.png` | 避免相对路径在组件中出错 |
| 回调地狱 | 强制使用 `async/await`,禁止 `success` 回调嵌套 | 可读性和错误处理 |

9.2.3 Git 提交规范与版本管理

采用 Conventional Commits 规范:

feat: 新增商品搜索功能

fix: 修复购物车数量计算错误

refactor: 重构请求拦截器

docs: 更新 README 组件文档

style: 统一代码格式(不影响逻辑)

perf: 优化列表渲染性能

test: 添加支付流程单元测试

chore: 更新依赖版本

配合 Git 分支策略(如 Git Flow 或 GitHub Flow),小程序的发布版本号应在提交审核前手动确认,与微信后台的"版本管理"保持一致。

9.3 调试与测试

9.3.1 开发者工具调试技巧:断点、日志、性能面板

断点调试

在开发者工具 Sources 面板中,找到对应页面的 JS 文件,点击行号即可设置断点。调试 `onLoad` 等初始化逻辑时,需要在断点处重新编译页面。

关键面板速查

|-----------------|---------------------|------------------------|
| 面板 | 用途 | 高频操作 |
| AppData | 查看/修改当前页面的 `data` | 调试数据绑定问题,手动改值看视图变化 |
| Storage | 查看/管理本地存储 | 清空缓存、查看 Token 是否正确写入 |
| Network | 抓包网络请求 | 查看请求头、响应体、耗时 |
| Performance | 录制性能数据 | 分析 `setData` 耗时、渲染帧率 |

9.3.2 真机调试与远程调试

参见 1.3.3 节。补充一点:Android 真机调试的 Console 输出比 iOS 更完整,iOS 在特定场景下可能吞掉部分日志。

遇到诡异问题时,优先在 Android 真机上复现和调试。

9.3.3 单元测试:Jest 集成

小程序单元测试的核心挑战是模拟 `wx` 全局对象。通过 `jest-mock-wx` 可以模拟大部分 API。

bash 复制代码
npm install jest babel-jest @babel/core @babel/preset-env --save-dev
javascript 复制代码
// __tests__/utils/request.test.js

/ 模拟 wx.request /

global.wx = {

  request: jest.fn()

}



const { buildUrl } = require('../../utils/request')

test('buildUrl 拼接参数', () => {

  const result = buildUrl('/api/user', { id: 1, name: 'test' })

  expect(result).toBe('/api/user?id=1&name=test')

})

可测试性的前提:

业务逻辑必须从组件/页面中抽离为纯函数。直接写在 `Page({})` 中的逻辑很难测试------这是推动架构分层的重要动力。

9.3.4 端到端测试

通过 `miniprogram-automator`(微信官方出品)可以实现自动化操作:

javascript 复制代码
const automator = require('miniprogram-automator')



const miniProgram = await automator.launch({

  projectPath: '/path/to/project'

})



const page = await miniProgram.currentPage()

await page.callMethod('handleTap')

const data = await page.data()

console.log(data.count)

端到端测试适合覆盖核心用户路径(登录→浏览→下单→支付),而非每个组件的细粒度测试。

9.4 项目架构设计

9.4.1 MVC/MVVM 架构在小程序中的应用

小程序的 `Page()` 和 `Component()` 本身就体现了 MVVM 思想:

  • Model(数据模型):`data` 和 `properties`

  • View(视图):WXML + WXSS

  • ViewModel(视图模型):`Page()`/`Component()` 中的方法和生命周期,负责处理数据和视图的桥接

实践中需要额外引入的分层:

页面/组件(View + ViewModel)

↓ 调用

Service 层(业务逻辑、状态管理)

↓ 调用

API 层(网络请求封装)

↓ 调用

后端接口

Service 层的引入是为了解决"页面中堆满业务逻辑"的问题。

例如购物车页面包含价格计算、优惠判断、库存校验等逻辑,这些应该放在 `services/cart-service.js` 中,页面只负责调用和渲染。

9.4.2 状态管理:原生方案 vs 第三方库

|--------------------------------------------|----------------|---------|---------------|
| 方案 | 适用规模 | 复杂度 | 说明 |
| globalData + 事件总线 | 小型项目(< 10 页面) | 低 | 简单直接,但数据流不可追踪 |
| getCurrentPages 传值 | 相邻页面 | 低 | 仅限页面间,组件不适用 |
| mobx-miniprogram | 中大型项目 | 中 | 响应式更新,自动同步视图 |
| @dcloudio/uni-state (uni-app | uni-app 项目 | 中 | Vuex 风格的状态管理 |

原生推荐方案(中型项目):结合 `globalData` 存放全局状态 + 事件总线通知变更 + Service 层封装修改逻辑。

页面在 `onShow` 中主动拉取最新状态,而非期待自动同步。

9.4.3 请求层封装与统一错误处理

7.1.3 节已给出完整的请求封装代码。此处强调架构层面的设计原则:

请求层的职责边界:

|---------------------|--------------------------|-------------------------------|
| 职责 | 归属 | 说明 |
| Base URL 拼接 | 请求层 | 环境切换时仅改一处 |
| Token 注入 | 请求层 | 避免每个页面手动带 Authorization |
| 401 统一处理 | 请求层 | 过期自动跳登录,无需页面关心 |
| 业务错误码映射 | 请求层 | `code: 10001` → "库存不足",统一提示 |
| Loading 控制 | 可选(通过参数 `showLoading`) | 静默请求关闭 Loading |

9.4.4 权限管理与路由守卫

小程序没有真正的"路由守卫"(如 Vue Router 的 `beforeEach`),但可以通过以下方案实现等效效果:

方案一:封装导航函数
javascript 复制代码
// utils/navigator.js

function navigateTo(url, options = {}) {

  / 检查是否需要登录 /

  if (options.requireAuth && !getApp().globalData.token) {

    / 未登录:跳转到登录页,登录成功后重定向回来 /

    wx.navigateTo({

      url: `/pages/login/login?redirect=${encodeURIComponent(url)}`

    })

    return

  }

  wx.navigateTo({ url, ...options })

}



module.exports = { navigateTo }
方案二:页面 Mixin(通过 behaviors)
javascript 复制代码
// behaviors/auth-behavior.js

module.exports = Behavior({

  lifetimes: {

    created() {

      / 页面创建时检查登录态 /

      if (!getApp().globalData.token) {

        wx.redirectTo({ url: '/pages/login/login' })

      }

    }

  }

})



// 需要登录的页面

Page({

  behaviors: [require('../behaviors/auth-behavior')],

  / 页面逻辑 /

})
方案三:`onShow` 检查

在每个需要登录的页面 `onShow` 中检查登录态,未登录则跳转。这是最原始但最显式的方案,适合页面数量少的项目。