本文目标
本文从真实团队项目角度,讲解微信小程序工程化实践,并设计一个覆盖入门、中级、高级知识点的综合 Demo。重点解决"能不能把项目长期维护好"的问题。
本章节知识图谱
基础能力: 页面/组件/API
工程结构: 分层目录
请求层: 统一封装
状态层: 用户/订单/配置
组件层: 通用组件/业务组件
错误处理: Loading/Empty/Error/Retry
缓存策略: 内存/本地/服务端
设计规范: 样式/交互/可复用
监控: 日志/埋点/异常
发布: CI/体验版/灰度/正式版
面试: 项目经验/工程治理
1. 工程化项目结构
是什么
工程化项目结构是把页面、组件、接口、状态、配置、工具、静态资源、测试和发布脚本按职责拆分,形成稳定边界。
为什么重要
小程序一旦进入多人协作和持续迭代,最大风险不是单个页面写不出来,而是功能越来越多后依赖混乱、重复封装、难以测试、难以排查问题。
底层原理
工程结构本质是依赖方向控制。页面可以依赖服务层和组件层,服务层可以依赖请求基础设施,基础设施不应反向依赖业务页面。
使用场景
- 团队项目初始化。
- 旧项目重构。
- 面试项目工程化升级。
- 多模块业务并行开发。
推荐结构
text
miniprogram/
app.js
app.json
app.wxss
config/
env.js
constants/
status.js
services/
request.js
auth.service.js
product.service.js
order.service.js
store/
user.store.js
cart.store.js
components/
base-button/
empty-state/
product-card/
order-card/
pages/
home/
product-detail/
cart/
order-list/
order-detail/
profile/
packages/
order/
marketing/
utils/
format.js
validator.js
logger.js
assets/
tests/
常见错误
- 页面直接调用
wx.request。 - 工具方法里混入业务逻辑。
- 组件既请求接口又控制页面跳转。
- 所有状态都放到
app.globalData。
最佳实践
- 页面层只做数据装配和交互控制。
- 服务层只负责业务 API。
- 请求层只负责协议、鉴权、错误处理。
- 组件层通过属性和事件通信。
- 全局状态只放真正跨页面共享的数据。
关联知识点
关联请求封装、状态管理、组件化、分包和架构设计。
面试考察方式
面试官常要求描述你负责项目的目录结构,并追问模块边界和依赖方向。
2. 请求层与错误处理
是什么
请求层负责统一管理接口调用、鉴权、错误处理、Loading、重试、日志和环境切换。
为什么重要
真实业务中接口失败不可避免。统一请求层能让错误处理一致,避免页面重复代码和遗漏安全逻辑。
底层原理
请求层对 wx.request 做 Promise 化包装,形成"请求前处理 -> 发起请求 -> HTTP 状态处理 -> 业务状态处理 -> 错误归一化 -> 日志上报"的流水线。
使用场景
- 所有服务端接口调用。
- 登录失效统一跳转。
- 网络失败重试。
- 接口耗时监控。
代码示例
js
// config/env.js
const ENV = "dev";
const map = {
dev: "https://dev-api.example.com",
test: "https://test-api.example.com",
prod: "https://api.example.com"
};
module.exports = {
ENV,
baseURL: map[ENV]
};
js
// services/request.js
const { baseURL } = require("../config/env");
class ApiError extends Error {
constructor(message, detail) {
super(message);
this.name = "ApiError";
this.detail = detail;
}
}
function request(options) {
const startedAt = Date.now();
const token = wx.getStorageSync("token");
return new Promise((resolve, reject) => {
wx.request({
url: `${baseURL}${options.url}`,
method: options.method || "GET",
data: options.data || {},
header: {
Authorization: token ? `Bearer ${token}` : "",
"content-type": "application/json",
...options.header
},
success(res) {
const cost = Date.now() - startedAt;
console.log("[api]", options.url, cost);
if (res.statusCode === 401) {
wx.removeStorageSync("token");
wx.reLaunch({ url: "/pages/login/index" });
reject(new ApiError("登录已失效", res));
return;
}
if (res.statusCode < 200 || res.statusCode >= 300) {
reject(new ApiError(`网络错误 ${res.statusCode}`, res));
return;
}
const body = res.data || {};
if (body.code !== 0) {
reject(new ApiError(body.message || "业务处理失败", body));
return;
}
resolve(body.data);
},
fail(err) {
reject(new ApiError("网络不可用,请稍后重试", err));
}
});
});
}
module.exports = {
request,
ApiError
};
常见错误
- 只处理成功路径。
- 所有错误都提示"系统错误"。
- 401 在每个页面单独处理。
- 没有记录接口耗时和失败上下文。
最佳实践
- 错误类型归一化。
- 登录失效统一处理。
- 页面保留错误态和重试能力。
- 关键接口记录耗时和 Trace ID。
关联知识点
关联登录、安全、监控、故障排查。
面试考察方式
常要求现场设计请求封装,并追问 Token 过期、接口重试、错误提示如何做。
3. 状态管理
是什么
状态管理用于维护跨页面共享数据,如用户信息、购物车、门店、权限、全局配置。
为什么重要
没有状态管理,页面之间会通过 globalData、缓存、路由参数互相传递数据,导致状态来源混乱。
底层原理
状态管理应遵循单一数据源和显式更新。页面订阅状态变化,状态更新后通知页面刷新局部视图。
使用场景
- 用户信息跨页面共享。
- 购物车数量同步。
- 门店切换影响多个页面。
- 权限和配置动态变化。
代码示例
js
// store/user.store.js
const listeners = [];
const state = {
userInfo: null,
token: wx.getStorageSync("token") || ""
};
function notify() {
listeners.forEach(listener => listener({ ...state }));
}
function subscribe(listener) {
listeners.push(listener);
listener({ ...state });
return () => {
const index = listeners.indexOf(listener);
if (index >= 0) listeners.splice(index, 1);
};
}
function setUserInfo(userInfo) {
state.userInfo = userInfo;
notify();
}
function setToken(token) {
state.token = token;
wx.setStorageSync("token", token);
notify();
}
module.exports = {
subscribe,
setUserInfo,
setToken
};
页面订阅:
js
const userStore = require("../../store/user.store");
Page({
data: {
userInfo: null
},
onLoad() {
this.unsubscribeUser = userStore.subscribe(state => {
this.setData({
userInfo: state.userInfo
});
});
},
onUnload() {
if (this.unsubscribeUser) {
this.unsubscribeUser();
}
}
});
常见错误
- 所有共享数据都放
globalData。 - 页面卸载后没有取消订阅。
- 状态更新入口分散。
- 缓存和内存状态不一致。
最佳实践
- 明确状态归属。
- 更新状态必须通过方法。
- 页面卸载取消订阅。
- 本地缓存只作为恢复状态的来源,不作为唯一真相。
关联知识点
关联架构设计、组件通信、缓存策略。
面试考察方式
常问小程序没有 Vuex/Redux 时如何管理跨页面状态。
4. 分包与包体治理
是什么
分包是把小程序代码按业务模块拆成主包和子包。主包负责启动和公共能力,子包按需加载。
为什么重要
小程序有包体限制。包体过大会影响启动速度和审核发布,也会让低端设备首屏体验变差。
底层原理
启动时先加载主包,进入分包页面时再下载对应分包。公共资源放主包会被多个分包复用,但放太多会拖慢首屏。
使用场景
- 订单、营销、会员、设置等低频模块。
- 大型小程序包体优化。
- 多业务模块独立迭代。
代码示例
json
{
"pages": [
"pages/home/index",
"pages/profile/index"
],
"subpackages": [
{
"root": "packages/order",
"pages": [
"list/index",
"detail/index"
]
},
{
"root": "packages/marketing",
"pages": [
"coupon/index",
"activity/index"
]
}
],
"preloadRule": {
"pages/home/index": {
"network": "wifi",
"packages": ["packages/order"]
}
}
}
常见错误
- 主包放太多业务页面。
- 分包之间互相引用页面代码。
- 公共组件重复打包。
- 分包预加载不看业务路径和网络条件。
最佳实践
- 首页、登录、Tab 页放主包。
- 低频重业务放分包。
- 公共组件和基础工具谨慎放主包。
- 分包预加载只服务高概率路径。
关联知识点
关联性能优化、架构模块拆分、发布治理。
面试考察方式
高频问题:如何优化小程序包体?分包怎么拆?
5. 组件库与样式规范
是什么
组件库是沉淀通用 UI 和交互模式的工程资产,样式规范用于统一视觉、间距、字体、颜色和交互状态。
为什么重要
没有组件库,团队会重复实现按钮、弹窗、卡片、空态、表单,造成体验不一致和维护成本上升。
底层原理
组件库通过属性定义可配置能力,通过事件暴露交互,通过样式隔离降低污染,通过文档和示例保证一致使用。
使用场景
- 多页面复用 UI。
- 团队统一设计语言。
- 快速搭建业务页面。
代码示例
js
// components/empty-state/index.js
Component({
properties: {
title: {
type: String,
value: "暂无数据"
},
actionText: {
type: String,
value: ""
}
},
methods: {
handleAction() {
this.triggerEvent("action");
}
}
});
xml
<view class="empty">
<image class="icon" src="/assets/empty.png" mode="aspectFit" />
<view class="title">{{title}}</view>
<button wx:if="{{actionText}}" bindtap="handleAction">{{actionText}}</button>
</view>
常见错误
- 组件参数过多,变成万能组件。
- 通用组件里写具体业务文案。
- 组件没有空态、禁用态、加载态。
最佳实践
- 组件保持职责单一。
- 通用组件不直接请求业务接口。
- 重要组件提供示例和使用约束。
- 对按钮、表单、弹窗、列表、空态优先建设。
关联知识点
关联 UI 规范、可维护性、团队协作。
面试考察方式
常问如何抽象组件、通用组件和业务组件如何区分。
综合 Demo 项目:本地生活服务小程序
项目背景
设计一个面向社区用户的本地生活服务小程序,用户可以浏览门店、购买服务、预约时间、支付订单、接收订阅消息、评价服务。商家端可通过后台管理商品、订单和运营活动。
业务目标
- 支撑真实服务预约和支付闭环。
- 覆盖列表、详情、登录、订单、支付、消息、评价等核心业务。
- 体现工程化、性能、安全、稳定性和架构设计能力。
- 可作为学习项目、团队培训项目和面试讲解项目。
功能列表
- 首页:推荐服务、附近门店、活动入口。
- 门店:门店列表、门店详情、地图定位。
- 服务:服务列表、服务详情、规格选择。
- 购物车:服务套餐、数量、价格计算。
- 订单:创建订单、订单列表、订单详情、取消订单。
- 支付:拉起微信支付、支付状态查询、失败重试。
- 消息:订阅消息授权、订单状态通知。
- 用户:登录、手机号授权、地址管理、评价。
- 运营:优惠券、活动页、分享。
- 稳定性:错误页、空态、重试、日志上报。
技术选型
- 原生微信小程序:适合深入理解平台机制。
- JavaScript 或 TypeScript:团队成熟后推荐 TypeScript。
- 自研轻量 Store:满足学习和中型项目。
- 服务端:Node.js/Java 均可,接口遵循 REST 或 BFF。
- 监控:接口耗时、页面错误、业务埋点。
项目结构
text
local-life-miniapp/
miniprogram/
pages/
home/
store-list/
store-detail/
service-detail/
cart/
login/
profile/
packages/
order/
list/
detail/
checkout/
marketing/
coupon/
activity/
components/
service-card/
store-card/
order-card/
price-bar/
empty-state/
services/
request.js
auth.service.js
store.service.js
service.service.js
order.service.js
payment.service.js
message.service.js
store/
user.store.js
cart.store.js
utils/
format.js
validator.js
logger.js
config/
env.js
核心模块设计
用户模块:
- 负责
wx.login、手机号授权、Token 存储、用户资料。 - 登录态由服务端签发,前端只保存业务 Token。
商品服务模块:
- 首页推荐、门店服务列表、详情。
- 支持缓存和分页。
购物车模块:
- 本地状态为主,提交订单前由服务端重新校验价格和库存。
订单模块:
- 创建订单、查询订单、取消订单、支付状态同步。
- 订单状态以后端为准。
支付模块:
- 前端拉起支付。
- 支付结果通过服务端回调确认。
- 前端支付后主动查询订单状态做兜底。
消息模块:
- 在用户明确操作时申请订阅消息。
- 服务端根据订单状态触发消息。
关键代码实现:订单创建与支付
js
// services/order.service.js
const { request } = require("./request");
function createOrder(payload) {
return request({
url: "/orders",
method: "POST",
data: payload
});
}
function getOrderDetail(orderId) {
return request({
url: `/orders/${orderId}`
});
}
module.exports = {
createOrder,
getOrderDetail
};
js
// services/payment.service.js
const { request } = require("./request");
function createPayment(orderId) {
return request({
url: `/orders/${orderId}/payment`,
method: "POST"
});
}
function requestPayment(payParams) {
return new Promise((resolve, reject) => {
wx.requestPayment({
...payParams,
success: resolve,
fail: reject
});
});
}
module.exports = {
createPayment,
requestPayment
};
js
// packages/order/checkout/index.js
const orderService = require("../../../services/order.service");
const paymentService = require("../../../services/payment.service");
Page({
data: {
submitting: false,
items: [],
addressId: ""
},
async submitOrder() {
if (this.data.submitting) return;
this.setData({ submitting: true });
try {
const order = await orderService.createOrder({
items: this.data.items,
addressId: this.data.addressId
});
const payParams = await paymentService.createPayment(order.id);
await paymentService.requestPayment(payParams);
wx.redirectTo({
url: `/packages/order/detail/index?id=${order.id}&from=pay`
});
} catch (err) {
wx.showToast({
title: err.message || "提交失败",
icon: "none"
});
} finally {
this.setData({ submitting: false });
}
}
});
配置说明
json
{
"permission": {
"scope.userLocation": {
"desc": "用于展示附近门店"
}
},
"requiredPrivateInfos": ["getLocation"],
"subpackages": [
{
"root": "packages/order",
"pages": ["list/index", "detail/index", "checkout/index"]
},
{
"root": "packages/marketing",
"pages": ["coupon/index", "activity/index"]
}
]
}
运行方式
- 使用微信开发者工具导入项目。
- 配置 AppID 和后端 API 地址。
- 在开发环境中开启"不校验合法域名"或配置合法域名。
- 启动 Mock 服务或连接测试环境接口。
测试方式
- 单元测试:测试
utils、服务层数据转换、状态管理。 - 接口测试:Mock 登录、订单、支付参数。
- 冒烟测试:首页、登录、详情、下单、订单列表。
- 真机测试:定位、支付、订阅消息、低端机性能。
部署方式
- 开发者工具上传体验版。
- 小程序 CI 上传版本。
- 体验版测试通过后提交审核。
- 正式发布前检查接口域名、隐私协议、版本号、灰度配置。
扩展方向
- 接入优惠券和会员等级。
- 增加商家客服和售后。
- 加入 WebSocket 实时订单状态。
- 增加运营活动配置平台。
- 接入性能监控和自动报警。
对应知识图谱
- 基础概念:页面、组件、配置。
- 核心能力:路由、请求、登录、存储。
- 工程实践:分层、组件库、状态管理、分包。
- 高级应用:支付、订阅消息、定位。
- 架构设计:订单、支付、消息模块边界。
- 性能安全:包体、缓存、支付安全、错误兜底。
实战案例:订单列表加载慢
业务背景
订单列表页需要展示用户历史订单,包含门店、服务、价格、状态、操作按钮。上线后用户反馈进入订单页慢,滚动卡顿。
问题描述
- 首次进入订单列表白屏 2 秒以上。
- 一次接口返回 100 条订单。
- 页面一次性
setData全量订单。 - 每个订单卡片包含多张图片。
技术难点
- 数据量大。
- 图片资源多。
setData数据过大。- 列表渲染节点多。
方案分析
方案一:只加 Loading。体验改善有限,不解决根因。
方案二:分页加载。降低单次请求和渲染成本。
方案三:图片压缩和懒加载。减少网络和渲染压力。
方案四:订单卡片字段裁剪。列表页只展示必要字段,详情页再查完整数据。
最终方案
- 接口改为分页,每页 10 条。
- 列表页只返回摘要字段。
- 图片使用缩略图。
- 下拉刷新重置第一页,上拉加载下一页。
setData只追加新页数据。
核心代码
js
Page({
data: {
orders: [],
page: 1,
pageSize: 10,
hasMore: true,
loading: false
},
onLoad() {
this.loadOrders(true);
},
onReachBottom() {
if (this.data.hasMore && !this.data.loading) {
this.loadOrders(false);
}
},
async loadOrders(reset) {
const page = reset ? 1 : this.data.page;
this.setData({ loading: true });
try {
const result = await Promise.resolve({
list: [],
hasMore: false
});
this.setData({
orders: reset ? result.list : this.data.orders.concat(result.list),
page: page + 1,
hasMore: result.hasMore
});
} finally {
this.setData({ loading: false });
}
}
});
性能、安全、可维护性权衡
- 性能:分页和摘要字段显著降低渲染压力。
- 安全:订单详情仍需服务端校验用户权限。
- 可维护性:列表接口和详情接口职责更清晰。
可能的坑
- 下拉刷新时未重置页码。
- 快速上拉触发重复请求。
- 删除订单后列表分页状态不一致。
线上故障排查思路
- 看接口耗时和返回体大小。
- 看
setData数据大小和频率。 - 真机观察图片加载和滚动帧率。
- 对比低端机和高端机表现。
面试中如何讲
先说明业务现象,再给出数据证据,然后解释根因是接口过大、渲染节点多和 setData 成本高,最后说明分页、摘要字段、图片优化和状态防重复提交的组合方案。
本章节面试题
题目:你会如何设计微信小程序项目结构?
- 难度:中级
- 高频:是
- 考察点:工程化、分层、可维护性。
- 标准答案:按页面、组件、服务、状态、工具、配置、静态资源拆分。页面层负责展示和交互,服务层负责业务接口,请求层负责协议和错误处理,状态层维护跨页面数据,组件层沉淀复用 UI。
- 深度扩展:可以进一步说明依赖方向、分包策略和团队协作规范。
- 常见错误回答:只列
pages和components,没有说明职责边界。 - 面试官追问:业务组件和通用组件如何区分?
- 项目应用场景:中大型业务小程序。
- 对应知识点:工程结构、组件库、服务层。
题目:如何封装一个可靠的请求层?
- 难度:中级
- 高频:是
- 考察点:接口封装、错误处理、登录态。
- 标准答案:对
wx.request做 Promise 封装,统一 baseURL、Header、Token、HTTP 状态、业务状态码、错误归一化、登录失效、耗时日志和重试策略。页面只感知业务数据和可展示错误。 - 深度扩展:高级回答会区分网络失败、HTTP 错误、业务错误和风控错误,并说明 Trace ID 和监控。
- 常见错误回答:只把回调改成 Promise。
- 面试官追问:401 和 403 如何处理?
- 项目应用场景:所有接口调用。
- 对应知识点:请求层、登录鉴权、监控。
题目:小程序状态管理怎么做?
- 难度:中级
- 高频:是
- 考察点:跨页面状态、数据一致性。
- 标准答案:简单项目可用
app.globalData和本地缓存;中型项目应建立状态模块,提供订阅、更新方法和清理机制。用户、购物车、门店、权限等跨页面数据应有明确归属,页面卸载时取消订阅。 - 深度扩展:可以讨论状态持久化、缓存恢复、登录退出清理。
- 常见错误回答:所有东西都放
globalData。 - 面试官追问:购物车数量如何在多个 Tab 同步?
- 项目应用场景:用户中心、购物车、门店切换。
- 对应知识点:状态管理、缓存、组件通信。
题目:如何做小程序分包?
- 难度:中级
- 高频:是
- 考察点:包体优化、业务拆分。
- 标准答案:首页、Tab、登录和公共基础能力放主包;订单、营销、设置等低频或重业务放分包。公共组件谨慎放主包,分包预加载要基于真实路径和网络条件。
- 深度扩展:可以说明独立分包、分包预下载和公共代码重复问题。
- 常见错误回答:为了分包而分包,不考虑业务访问路径。
- 面试官追问:分包之间能不能直接互相引用?
- 项目应用场景:大型商城、本地生活、政企服务小程序。
- 对应知识点:分包、性能优化、架构拆分。
题目:订单列表卡顿你怎么排查?
- 难度:高级
- 高频:是
- 考察点:性能定位、工程经验。
- 标准答案:先看接口耗时、返回体大小、首屏数据量、图片数量、
setData频率和大小,再看渲染节点数量和真机表现。常见方案包括分页、摘要字段、图片压缩和懒加载、骨架屏、局部更新、避免重复请求。 - 深度扩展:专家回答会结合双线程通信成本解释
setData为什么影响性能。 - 常见错误回答:只说"加缓存"。
- 面试官追问:如果接口很快但页面仍卡,下一步看什么?
- 项目应用场景:订单、商品、消息、评论长列表。
- 对应知识点:列表性能、setData、工程监控。
工程高级知识点库
文档定位
本文扩展工程实践、高级能力和真实项目治理知识点,适合团队开发规范、项目重构、面试项目讲解和技术方案评审。
工程高级知识图谱
工程结构
请求治理
状态治理
组件治理
鉴权/重试/错误
缓存/一致性
组件库/设计规范
监控/日志
团队协作
发布/灰度/复盘
架构演进
1. 环境配置治理
是什么
环境配置治理是把开发、测试、预发、生产的接口域名、开关、日志级别和 Mock 策略统一管理。
为什么重要
环境混乱会导致测试连生产、生产连测试、日志泄露、接口行为不一致。
代码示例
js
const ENV = "dev";
const configs = {
dev: {
baseURL: "https://dev-api.example.com",
mock: true,
logLevel: "debug"
},
test: {
baseURL: "https://test-api.example.com",
mock: false,
logLevel: "info"
},
prod: {
baseURL: "https://api.example.com",
mock: false,
logLevel: "warn"
}
};
module.exports = configs[ENV];
常见错误
- 在页面里硬编码接口地址。
- 生产环境打开 Mock。
- 日志级别不区分环境。
最佳实践
- 环境配置集中管理。
- 发布前自动检查环境。
- 敏感配置不放前端。
- 远程开关要有默认值。
2. API 分层设计
分层模型
| 层级 | 职责 | 示例 |
|---|---|---|
| 请求基础层 | HTTP、Token、错误、日志 | request.js |
| 服务层 | 业务接口语义 | order.service.js |
| 页面层 | 调用服务并渲染 | pages/order |
| 状态层 | 跨页面状态 | cart.store.js |
反例
js
Page({
onLoad() {
wx.request({
url: "https://api.example.com/orders",
success: res => this.setData({ orders: res.data.data })
});
}
});
问题:页面耦合域名、协议、返回结构和错误处理。
正确写法
js
const orderService = require("../../services/order.service");
Page({
async onLoad() {
const orders = await orderService.listOrders();
this.setData({ orders });
}
});
3. 错误状态模型
是什么
页面不应只有 loading 和 success,还应明确 empty、error、offline、forbidden 等状态。
状态模型
js
const PageStatus = {
LOADING: "loading",
SUCCESS: "success",
EMPTY: "empty",
ERROR: "error",
FORBIDDEN: "forbidden"
};
页面示例
js
Page({
data: {
status: "loading",
list: []
},
async loadData() {
this.setData({ status: "loading" });
try {
const list = await Promise.resolve([]);
this.setData({
list,
status: list.length ? "success" : "empty"
});
} catch (err) {
this.setData({ status: "error" });
}
}
});
面试追问
- 为什么不建议所有错误都 Toast?
- 空态和错误态有什么区别?
- 页面错误态如何设计重试?
4. 组件分层与复用边界
组件分类
| 类型 | 职责 | 示例 |
|---|---|---|
| 基础组件 | 通用视觉和交互 | Button、Empty |
| 业务组件 | 绑定业务语义 | OrderCard |
| 容器组件 | 聚合状态和数据 | CartPanel |
| 页面组件 | 页面局部结构 | HomeSection |
判断标准
- 多个页面重复出现,可抽基础或业务组件。
- 只在一个页面出现但结构复杂,可抽页面组件。
- 组件需要直接请求业务接口时,优先评估是否应该放页面或容器。
组件设计清单
- 输入是否通过
properties。 - 输出是否通过
triggerEvent。 - 是否有 loading、empty、disabled 状态。
- 是否写死业务文案。
- 样式是否污染外部。
- 是否可单独预览和测试。
5. 购物车状态设计
业务问题
购物车涉及跨页面数量同步、价格计算、库存校验、登录态、服务端一致性。
推荐设计
- 本地 store 维护当前选择。
- 本地缓存用于恢复。
- 提交订单前由服务端重新计算价格和库存。
- 商品变价、下架、库存不足必须以后端为准。
代码示例
js
const state = {
items: []
};
function addItem(service) {
const found = state.items.find(item => item.id === service.id);
if (found) {
found.count += 1;
} else {
state.items.push({ ...service, count: 1 });
}
wx.setStorageSync("cart", state.items);
}
function getTotalPrice() {
return state.items.reduce((sum, item) => sum + item.price * item.count, 0);
}
module.exports = {
state,
addItem,
getTotalPrice
};
6. 分包策略扩展
拆分原则
| 模块 | 建议位置 | 原因 |
|---|---|---|
| 首页 | 主包 | 启动入口 |
| 登录 | 主包 | 高频基础流程 |
| Tab 页面 | 主包 | 平台要求和高频 |
| 订单详情 | 分包 | 相对低频 |
| 营销活动 | 分包 | 资源重、变动频繁 |
| 设置页 | 分包 | 低频 |
分包风险
- 公共依赖过大导致主包变大。
- 分包首进有加载等待。
- 分包间引用路径混乱。
- 预加载策略过度,反而浪费流量。
7. Mock 与接口联调
是什么
Mock 是在后端未完成或接口不稳定时,用模拟数据支持前端开发和测试。
实践建议
- Mock 数据结构必须贴近真实接口。
- Mock 和真实接口共用服务层。
- Mock 不应污染生产环境。
- 复杂流程用场景化 Mock,例如支付成功、支付失败、库存不足。
示例
js
function mockOrderDetail(id) {
return Promise.resolve({
id,
status: "PENDING_PAY",
amount: 9900,
items: [{ name: "深度清洁服务", count: 1 }]
});
}
8. 日志与埋点
日志分类
| 类型 | 用途 | 示例 |
|---|---|---|
| 技术日志 | 排查错误 | JS 异常、接口失败 |
| 行为埋点 | 分析路径 | 点击、曝光、转化 |
| 性能日志 | 优化体验 | 首屏耗时、接口耗时 |
| 业务日志 | 追踪链路 | 下单、支付、退款 |
关键字段
- 用户匿名 ID。
- 页面路径。
- 基础库版本。
- 设备型号。
- 网络类型。
- 接口 Trace ID。
- 错误码和堆栈。
代码示例
js
function report(event, payload) {
const system = wx.getSystemInfoSync();
console.log("[report]", {
event,
payload,
page: getCurrentPages().slice(-1)[0]?.route,
sdk: system.SDKVersion,
model: system.model,
time: Date.now()
});
}
module.exports = { report };
9. 小程序 CI 与发布流程
标准流程
- 本地开发自测。
- 分支代码审查。
- 自动静态检查。
- 上传体验版。
- QA 冒烟测试。
- 提交审核。
- 灰度或观察发布。
- 监控错误率。
发布检查清单
- API 域名正确。
- AppID 正确。
- 版本号和更新说明正确。
- 隐私协议和权限声明完整。
- 支付、登录、订阅消息真机验证。
- 关键页面无白屏。
- 远程开关默认值可兜底。
10. 工程面试题扩展
题目:为什么页面不应该直接调用 wx.request?
- 难度:中级
- 高频:是
- 考察点:工程分层。
- 标准答案:页面直接调用会耦合域名、协议、Token、错误处理和返回结构,导致重复代码和维护困难。应通过请求层和服务层统一封装。
- 深度扩展:请求层还能接入日志、Trace ID、重试和登录失效处理。
- 常见错误回答:直接调更方便。
- 面试官追问:服务层和请求层的区别是什么?
- 项目应用场景:团队项目、长期迭代项目。
- 对应知识点:API 分层、请求治理。
题目:如何判断一个组件是否应该抽象?
- 难度:中级
- 高频:是
- 考察点:组件设计。
- 标准答案:看复用频率、变化方向、职责是否单一、输入输出是否清晰。如果只是代码片段相似但业务语义不同,不一定要抽;如果多个页面共享同一交互和视觉规范,应抽组件。
- 深度扩展:过早抽象会让组件参数膨胀,降低可维护性。
- 常见错误回答:重复两次就抽。
- 面试官追问:万能组件有什么问题?
- 项目应用场景:组件库、业务卡片、弹窗。
- 对应知识点:组件治理。
题目:如何设计小程序发布前检查?
- 难度:高级
- 高频:否
- 考察点:质量保障。
- 标准答案:从配置、权限、接口、真机、关键链路、性能、错误监控、灰度开关和回滚方案检查。支付、登录、定位、订阅消息必须真机验证。
- 深度扩展:发布不是上传动作,而是风险控制流程。
- 常见错误回答:开发者工具能跑就提交审核。
- 面试官追问:线上出现严重问题如何止血?
- 项目应用场景:正式上线、版本迭代。
- 对应知识点:CI、灰度、稳定性。