程序猿必看:5步实战开发微信小程序,从零搭建到上线
很多程序猿第一次接触微信小程序开发时,容易掉进“会写代码但不会用微信工具”的坑里。你可能有扎实的 JavaScript 或 Vue 基础,但小程序不是单页应用,它有自己的生命周期、组件通信和云开发逻辑。下面我会从实战角度出发,用讲课的方式拆解开发流程,尽量避开那些官方文档里翻来覆去讲的废话。
一、从“注册”到“真机调试”,别在第一步踩坑
我知道你肯定觉得注册账号很简单,但很多老手在这步翻车是因为“AppID 权限”没搞对。注册时选“个人”还是“企业”?如果你只是练手,个人号完全够用,但个人号无法使用微信支付和大部分云开发高级功能。企业号需要营业执照,不过可以开通“虚拟支付”。我建议你直接注册企业号,哪怕挂个朋友的执照,因为后期要上线带支付功能的小程序,个人号会让你重写代码。
拿到 AppID 后,下载微信开发者工具。这里有个冷知识:不要用最新版!比如 2024年11月的某个版本有云开发缓存 bug,导致上传代码后数据不更新。你可以去官网的“历史版本”里选一个稳定版(比如 Stable 1.06.2409140)。打开工具后,选“小程序项目”,填入 AppID,开发模式选“不使用云服务”——除非你确定要用云数据库,否则先别开云开发,后面手动配更灵活。
二、项目结构别照搬 Vue,要理解“双线程”架构小程序不像网页那样直接操作 DOM,它分“渲染层”和“逻辑层”。你写的 .wxml 文件在渲染层跑,.js 文件在逻辑层跑,两边通过“数据绑定”通信。这导致一个经典问题:你不能在 .wxml 里直接调用函数,比如 <view onclick="handleClick()"> 是错的,必须写成 <view bindtap="handleClick">,而且 handleClick 必须在 .js 的 Page 对象里定义。
我见过不少前端同事把 Vue 的 v-for 习惯带进来,在小程序里写成 <view wx:for="{{list}}" wx:for-item="item">,但忘了加 wx:key。如果不加 key,列表更新时整个组件会重新渲染,性能直接崩掉。正确的写法是:<view wx:for="{{list}}" wx:for-item="item" wx:key="index">,或者用 item 里的唯一 id 字段。顺便提一嘴,wx:key 的值不能是保留字,比如 “id” 可以,但 “key” 不行。
早期小程序文档推荐用 <template> 复用代码,但 template 没有独立的作用域和生命周期,你没法在里面使用 onLoad 或 properties。现在官方主推“自定义组件”,你需要在根目录的 app.json 里声明组件路径,或者直接在页面的 json 文件里注册。
举个例子,你要做一个“商品卡片”组件。先在 components 文件夹下建一个 product-card 文件夹,里面放 product-card.js、product-card.json、product-card.wxml、product-card.wxss。在 product-card.json 里写 {"component": true}。然后在父页面的 json 文件里注册:{"usingComponents": {"product-card": "/components/product-card/product-card"}}。父页面传参时用 <product-card product="{{item}}" bind:addtocart="onAddToCart"></product-card>,子组件通过 properties 接收 product 对象,并通过 this.triggerEvent('addtocart', {id: this.properties.product.id}) 向父页面发送事件。这套模式比 Vue 的 props 和 emit 更啰嗦,但胜在稳定。
用云开发只是当数据库用,但云函数的价值远不止 CRUD。比如你要做一个“每日签到”功能,需要每天凌晨重置签到状态。传统做法是写个定时脚本跑在服务器上,但用云开发的话,你可以在云函数里配置“定时触发器”。在云函数目录下创建 config.json,写上 {"triggers": [{"name": "dailyReset", "type": "timer", "config": "0 0 0 * * * *"}]},这个 cron 表达式表示每天零点执行。云函数里直接调用数据库更新所有用户的签到字段,完全不需要维护额外服务器。
另外,云函数的冷启动问题很烦人。如果用户第一次访问时触发云函数,可能要等 2-3 秒才能返回结果。解决办法是在小程序启动时预加载云函数:在 app.onLaunch 里调用一个空云函数,比如 wx.cloud.callFunction({name: 'warmUp'}),这样后续调用会快很多。这个 warmUp 函数里甚至可以什么都不做,只返回一个 “ok”。
小程序有 5 种跳转方式,但最常用的是 navigateTo 和 switchTab。如果你跳转的是带 tabBar 的页面,比如首页、分类页,必须用 switchTab,否则页面会卡在跳转前的逻辑里。如果你跳转的是普通页面,比如商品详情页,用 navigateTo,它会保留当前页面栈,方便返回。
但有个坑:navigateTo 最多只能打开 10 层页面,超过 10 层后跳转会失败。解决办法是用 redirectTo 代替,它会关闭当前页面再跳转,不增加页面栈深度。比如用户从首页 -> 商品列表 -> 商品详情 -> 下单页,这个链路里“下单页”可以用 redirectTo 跳转,这样用户从下单页返回时直接回到商品列表,而不是商品详情,体验更合理。
另外,页面间传参不要用全局变量,用 url 的 query 参数。比如 wx.navigateTo({url: '/pages/detail/detail?id=123'}),然后在目标页的 onLoad 里通过 options.id 获取。如果参数很长,比如传一个对象,建议用 encodeURIComponent(JSON.stringify(obj)) 编码,再在目标页解码。但要注意 url 长度限制在 1024 字符以内,超长的话改用 wx.setStorageSync 存到本地,跳转后再读取。
小程序的 setData 是性能杀手。每次调用 setData,逻辑层会把数据传给渲染层,如果数据量太大或者频率太高,页面会明显卡顿。一个常见错误是在 onPageScroll 事件里频繁 setData,比如实时更新滚动位置。解决办法是使用“数据监听器”或者“节流函数”。
比如你要实现一个“回到顶部”按钮,只在滚动超过 500px 时显示。可以在 onPageScroll 里用 throttle 限制 setData 频率:
let lastTime = 0;
Page({
onPageScroll(e) {
const now = Date.now();
if (now - lastTime > 200) {
lastTime = now;
this.setData({scrollTop: e.scrollTop});
}
}
});
另一个技巧是“精简数据”。setData 时只传变化的部分,不要传整个对象。比如你有一个列表 list,用户点击某个 item 后要修改它的状态,不要写 this.setData({list: newList}),而是写 this.setData({['list[0].status']: 'done'}),这样渲染层只会更新那个 item,而不是整个列表。
开发者工具里自带 vConsole,但不知道可以在真机上打开。在小程序代码里加一行 wx.setEnableDebug({enableDebug: true}),然后在真机上摇一摇手机,就能看到 vConsole 面板,可以查看网络请求、storage 数据、页面结构。这个功能在排查“真机环境 vs 开发工具环境”差异时特别好用。
另外,如果你需要在 wxml 里处理一些复杂逻辑,比如格式化时间、过滤数组,不要用 wx:if 套太多条件,而是用 WXS(WeiXin Script)。WXS 是运行在渲染层的脚本,可以编写自定义函数。比如在 .wxs 文件里写一个格式化函数:
function formatTime(dateStr) {
var date = getDate(dateStr);
var year = date.getFullYear();
var month = date.getMonth() + 1;
return year + '-' + month;
}
module.exports = {formatTime: formatTime};
然后在 wxml 里引入:<wxs src="../../utils/filter.wxs" module="filter" />,再调用 {{filter.formatTime(item.createTime)}}。这样避免了在 js 里处理数据再 setData 的额外开销。
小程序审核很严格,尤其是“社交”和“支付”类目。如果你做的不是纯粹的工具类小程序,比如带评论、点赞功能,就需要选择“社交”类目,但个人号不能选社交类目,必须企业号。另外,不要在小程序里做“虚拟商品”的支付,比如卖会员、卖积分,微信会直接拒审。解决方案是用“广告变现”或者“实物商品”绕过,比如用户通过观看广告获得积分,积分只能兑换实物。
一个容易被忽略的点:小程序的“用户隐私协议”必须明确列出收集了哪些信息。如果你用了 wx.getUserInfo 或 wx.getLocation,必须在弹窗里说明用途,否则审核会被打回。建议在 app.onLaunch 里判断是否已经弹过协议,没弹过就显示一个自定义弹窗,用户同意后才调用相关 API。
最后提一下“代码包大小”。小程序限制 2MB 以内,超过的话上传会失败。你可以用“分包加载”解决:在 app.json 里配置 subPackages,把不常用的页面(比如帮助中心、关于我们)放到子包,用户访问时才下载。子包大小不能超过 2MB,总包大小不能超过 20MB。这个机制和 Webpack 的 code splitting 类似,但配置更傻瓜化。
开发小程序的过程,本质上是在微信的“规则围墙”里跳舞。你越熟悉它的边界,就越能写出高效、少 bug 的代码。别急着抄网上的模板,先把上面这些细节刻在脑子里,遇到问题你才能自己拆解,而不是去百度上翻那些千篇一律的“首先、其次、再者”。

