有一次一位开小餐馆的朋友问我想做一个点餐小程序问我该从哪里开始。我第一反应不是递给他一份代码而是先问了他三个问题顾客为什么要打开这个小程序用过一次之后凭什么再打开第二次如果上线后突然用不了你能接受吗他愣了很久然后说“我没想过”。这不是个例。我见过程序员朋友想把小程序当成App来写设计完十几个页面才动手也见过运营朋友套了个模板就上线结果审核被拒了三次都不知道原因。微信小程序是一个门槛看起来很低、实际水深得很的东西。它不要求你懂复杂的系统知识但它要求你同时想清楚产品逻辑、代码实现、平台规则这三件事。这篇文章不打算做一本百科手册而是按我自己从零开始做小程序的完整路径把“做小程序”这件事拆成六个关键环节原理认知、立项准备、页面搭建、数据交互、发布上线、上线后迭代。每一个环节我都写清楚当时为什么这样选、踩过什么坑、现在会怎么处理。适合第一次打算做小程序的人也适合已经写了一半但总觉得哪里不对的人。1. 微信小程序到底是什么它和App、H5的本质差别1.1 双线程模型与“数据驱动”思维很多第一次接触微信小程序的人会下意识把它当成“一个网页”。毕竟打开以后看到的也是页面点来点去也是跳转好像就是换了层皮的H5。这个直觉不算错但如果你按写网页的思路去写小程序很快就会踩到一个大坑你不能直接操作DOM。小程序不是由浏览器直接加载一份HTML页面而是运行在微信提供的容器里。这个容器和普通浏览器的区别最核心的就是双线程模型视图层和逻辑层是分开的。你在开发工具里面写WXML它会被编译成一套视图渲染结构你写的JavaScript则运行在另一个逻辑线程里。两层之间靠数据通信来同步你通过setData把数据从逻辑层传给视图层视图层再把用户事件传回逻辑层。“数据驱动”这四个字是小程序和小程序里最重要的思维转换。你在H5里让某个文字变色可以直接用jQuery查一下DOM改一个class但小程序里不能用字符串拼HTML也不能按ID去改样式。你唯一应该做的事情是维护好一份数据状态页面根据状态自动重新渲染。比如做一个购物车角标网页思维是“找到右上角的数字把它改成3”小程序思维是“把购物车数量这个变量改成3页面自动更新”。听起来只是说法不一样实际开发效率天差地别。数据模型理清楚了页面越写越顺手数据模型一塌糊涂就会陷入“改了一个地方、另一个地方莫名其妙变了”的噩梦。提示如果你是从零学小程序第一件事不是背组件而是接受“一切皆数据”的观念。写任何交互之前先在纸上画出这个页面需要哪些状态数据再想UI怎么展示。1.2 包体积限制、更新机制与生态位置小程序还有一个和网页、App都不像的地方它有体积限制而且更新需要“过一道审核”。我最早做小程序的时候把一套后台管理系统的页面全塞进去结果上传代码的时候直接提示包太大。小程序主包的体积限制是2MB整体使用分包机制可以扩展但也远没有App那样随意。这意味着你在规划阶段就要想清楚哪些页面必须在小程序里哪些功能放到公众号文章或外部网页里更合适。这不是技术上的妥协而是产品定位的取舍——小程序本来就不适合承载太重的东西。更新机制也是一道硬约束。普通的网页部署完就生效App可以强制更新版本但小程序的代码上传后要经过平台审核审核通过后用户才会逐步拿到新版本。很多团队第一次做小程序把“版本发布”想象成往服务器上丢一个压缩包结果提审前才发现一堆隐患。小程序在微信生态里的位置概括成一句话就是低频、轻量、用完即走。它不是要替代用户的桌面App而是在用户需要某个具体服务的时候扫码就能打开用完关掉也不心疼。理解了这个定位你就不会做出一个500MB的“超级小程序”。1.3 一台表格看清小程序、H5、App的差别我自己在给团队讲技术选型时常用下面这张表来讨论。不一定适用于所有场景但对大多数中小团队做决策很有参考价值。对比维度微信小程序H5网页原生App安装成本无需安装扫码即用打开链接即用需要下载安装入口路径微信内搜索、扫码、分享卡片浏览器的地址栏、链接点击桌面图标点击开发门槛中等有专门的语法体系低前端技术栈可用高需要系统开发技能更新速度需要提审通过后才生效随时部署随时生效上架应用商店审核系统能力受微信开放能力限制受浏览器能力限制系统级能力最完整用户留存依赖微信生态留存弱依赖浏览器入口留存弱依赖桌面图标留存最强体积限制2MB级别可分包几乎无限制数百MB也常见支付能力微信生态内有成熟方案受限于浏览器环境各平台支付接入这张表最想表达的一点是不存在“哪个绝对更好”的问题只有“哪个更适合你的业务场景”。如果是内容型产品团队又赶时间H5往往是性价比最高的。如果用户的场景大多在线下扫码、在微信里聊天分享小程序会是更灵巧的中间形态。如果要做重度使用、依赖系统能力的长期工具那就老老实实做App。2. 立项前需要想清楚的边界问题比写代码重要2.1 注册主体与小程序账号类型动手写代码之前先决定用谁的身份注册小程序。这不是走个流程那么简单它直接决定了你的小程序能选哪些类目、能开哪些能力、审核的严格程度。常见的主体类型有个人主体和企业主体。个人主体注册方便、不用营业执照但类目范围比较窄很多涉及行业资质的功能都没法申请比如在线支付类、医疗健康类、金融类这些基本都要求企业主体甚至特殊资质。企业主体需要营业执照、对公账户验证等流程门槛高一些但能力边界宽很多。我的建议是如果这个项目将来要正经运营不要省这一步直接用企业主体注册。等小程序做出来有点量了再去换主体是一件非常痛苦的事。账号迁移涉及用户数据、openid映射、审核状态折腾周期很长稍有不慎还会丢用户。如果只是自己练手、学技术注册一个个人主体的小程序或者直接在官方开发者工具里用“测试号”模式完全够用来学习页面开发、组件用法和接口调用。2.2 类目选择决定了你的审核难度很多新手注册完账号看到后台有一个“选择类目”的选项随手选了一个“生活服务”。等到提审那天才发现这个类目需要额外上传资质文件自己根本没有于是项目卡在审核环节白白浪费一周。我在踩过这个坑之后养成一个习惯动工之前先去看平台规则里的“运营类目”对照表。打开后台找到“服务类目”它会告诉你每个类目对应的资质要求。比如你要做一个服装电商平台通常会要求提供品牌授权或相关经营资质你要做一个外卖点餐很多地方需要有对应的餐饮行业许可。这不是程序能解决的问题是运营资质问题。对个人开发者来说更稳妥的做法是选择比较通用的类目比如“工具”“教育非学历培训”“生活服务”里那些不要求特殊资质的小类。注意有些类目看起来普通但点进去会有细分要求这些都是提审时候的“隐藏炸弹”。提前选好类目比上线前临时换类目要省心得多。2.3 技术路线原生开发还是跨端框架决定好账号和类目后接下来是技术路线选择。最核心的分叉口是直接用原生小程序语法还是用跨端框架写一套代码编译到多个平台。原生小程序语法的好处是性能可控、踩坑资料多、和平台能力同步速度最快。官方文档每次更新能力原生语法总是最先接上的。缺点是它只在微信生态里运行如果你想同时做支付宝小程序、抖音小程序、各类快应用原生代码没法复用。跨端框架的好处是用一套前端代码编译到多个平台适合“一个产品多处投放”的场景。但代价是框架本身是一层抽象很多平台特性要用框架提供的兼容API才能拿到遇到线上bug时你还得先判断是框架编译问题、平台渲染问题还是自己业务的问题排查链路会长很多。我的建议是第一次做小程序老老实实用原生。原因不是跨端框架不好而是你还没踩过小程序的坑直接用抽象层会把“平台问题”和“框架问题”混到一起很难建立正确的排查直觉。等你原生写过一两个完整项目再评估要不要上跨端框架那时候你的取舍会清晰很多。提示还有一个常见的坑叫“模板套用”。网上有大量现成的小程序商城模板看起来很完整。但模板改起来往往比从零写还痛苦因为你不理解它的数据结构、事件流和埋点逻辑。模板适合“参考交互”不适合“直接生产”。3. 页面搭建和文件体系从最小可运行项目到组件化3.1 最小项目长什么样四个文件各司其职我见过很多教程教你一上来就创建项目然后粘贴一堆代码。结果初学者连文件之间的关系都没搞懂后面一报错就不知道去哪里排查。所以我建议从最小项目开始把每一个文件的角色弄清楚。一个页面由四个文件组成命名相同、后缀不同。.wxml负责页面结构相当于HTML.wxss负责样式相当于CSS.js负责页面逻辑和数据.json负责页面级别的配置比如页面标题栏样式。页面文件放在pages目录下哪个页面是首页、全局窗口长什么样由全局配置文件app.json统一声明。一个最小的全局配置大概是这样的{ pages: [ pages/index/index, pages/detail/detail ], window: { navigationBarTitleText: 示例小程序, navigationBarBackgroundColor: #f5f5f5 } }注意pages数组里的第一项就是启动页面。很多人改了半天代码发现打开小程序还是旧页面结果是因为第一项没设置对。一个最简单的页面逻辑用Page构造函数注册Page({ data: { count: 0 }, onTap() { this.setData({ count: this.data.count 1 }); } });对应的 WXML 里只需要声明一个文本和一个按钮view classcontainer text{{count}}/text button bindtaponTap点我/button /view这个例子看起来简单但它包含了小程序开发中最核心的闭环数据定义在data里模板通过插值表达式{{}}绑定数据用户事件通过bindtap绑定到方法方法里通过setData更新数据数据一变界面自动更新。3.2 页面生命周期和组件的复用边界写小程序绕不开生命周期。页面从打开到销毁会依次触发onLoad、onShow、onReady、onHide、onUnload这些事件。最常见的使用方法是onLoad里拿参数、拉取初始数据onShow里处理从其他页面返回后的数据刷新onUnload里做清理操作。一个让新手困惑的问题是数据请求该写在onLoad还是onShow我的习惯是首次进入页面需要用页面参数才能确定的请求写onLoad从二级页面返回后需要刷新的请求写onShow。两个都写会出现重复请求所以在团队项目里这个边界一定要定下来。当页面里的内容开始重复比如列表卡片、空状态、底部按钮就该抽成自定义组件了。自定义组件用Component构造函数定义需要在组件的.json文件里加上一句{ component: true }组件之间通信靠properties接收外部传入的数据靠triggerEvent向父级抛事件。我自己踩过的一个坑是在自定义组件文件里用了Page而不是Component结果组件加载不出来控制台也没有明显报错。后来才知道组件文件必须用组件构造器注册。3.3 rpx适配与样式边界小程序里的尺寸单位是一个容易忽略的细节。官方推荐的rpx以屏幕宽度为基准设计稿宽度如果是750直接按设计稿的数值写就行在不同分辨率的手机上会自动缩放。不用rpx而用px在高端机和低端机上很容易出现页面左右不对齐。样式方面WXSS基本兼容常见CSS语法但有一些边界限制。最明显的是带标签名的选择器比如div、p这类标签选择器不能直接用在小程序和原生组件上你只能给view、text加 class 或 id 去选中。另外全局通配符*的适配在小程序环境里并不完全可靠布局尽量用 flex 一套思路贯穿到底。开发微信小程序的导航栏还有一个特殊点右上角的胶囊按钮是微信自己附加在原页面上的不可去掉。所以设计页面顶部布局时不要想当然地认为整个屏幕顶部都是你的内容区。很多新手做自定义导航把按钮位置计算错结果被胶囊遮住一半就是这个原因。4. 数据请求、登录态与常见坑前后端衔接是关键4.1 网络请求的基本封装小程序里发请求用wx.request但它是一个基于回调的API直接写在业务代码里容易产生回调地狱。我习惯在一开始就做一个简单封装把它包装成 Promise。const baseUrl https://api.example.com; function request(path, method GET, data {}) { return new Promise((resolve, reject) { wx.request({ url: ${baseUrl}${path}, method, data, header: { Content-Type: application/json, Authorization: wx.getStorageSync(token) || }, success: (res) { if (res.statusCode 200) { resolve(res.data); } else { reject(res); } }, fail: reject }); }); } module.exports { request };这个封装虽然简单但至少解决了三个问题统一了请求入口、把回调转成了 Promise、把登录 token 的读取集中到了一个地方。后续如果要做统一错误提示、统一登出逻辑也是在改动这个文件。真正让人头疼的不是封装而是配置。小程序对请求域名有严格的白名单机制上线后wx.request的请求地址必须是已配置到后台的合法域名并且必须是 HTTPS。开发阶段你可以在官方开发者工具里勾选“不校验合法域名”来方便调试但上线前一定要去后台把正式域名配好。我见过最典型的翻车现场是开发阶段一直开着“跳过域名校验”本地一切正常代码也上传了结果打开线上版本所有接口全部失败。原因就是上线后没有配置合法域名。这个坑说大不大但排查起来很容易让人怀疑人生因为手机屏幕上没有任何错误码只在调试工具里能看到一条url not in domain list。4.2 登录态到底怎么拿token如何存小程序的登录和传统网页登录长得不太一样。没有“输入密码”这一步它的核心是让微信帮你确认“这个人是谁”。标准流程是前端调用wx.login获取一个临时凭证code把它发给后端后端拿着code调用平台的接口换回来用户的openid和session_key。openid是用户在小程序生态里的唯一标识session_key是解密用户敏感数据的钥匙。关键问题来了后端拿到openid后不应该直接把openid返回给前端作为登录凭证更不应该把session_key发给前端。正确的做法是后端自己生成一个自定义的token比如一个加了过期时间的随机字符串把它和openid关联存起来然后把token返回给前端。前端把这个token用wx.setStorageSync存起来后续每个请求都带上它。为什么不只存在全局变量里因为小程序在用户切后台、内存紧张时会被微信回收下次重新启动全局变量里的token已经丢了但storage里的数据还在。从storage读取再配合接口返回401时重新走一遍登录流程是应对小程序环境最稳妥的方案。function ensureLogin() { const token wx.getStorageSync(token); if (token) { return Promise.resolve(token); } return new Promise((resolve, reject) { wx.login({ success: async (res) { if (res.code) { const data await request(/auth/login, POST, { code: res.code }); wx.setStorageSync(token, data.token); resolve(data.token); } else { reject(res); } }, fail: reject }); }); }这套流程我写了很多次最想提醒的就一句话不要把平台返回的敏感数据当成自己的业务凭证。你只把code当一次性钥匙用就好剩下的事情让后端去处理。4.3 本地存储与“页面来自哪里”的状态接力小程序的wx.setStorageSync很适合存一些轻量的用户偏好比如列表页每页显示条数、深色模式开关、购物车草稿。但不要往storage里塞大对象它的容量有限而且每次同步写入大对象在低端机上会有明显卡顿。还有一个小技巧当页面之间需要传递复杂对象时不要只依赖 URL 参数。URL 参数只能传字符串而且长度有限。如果从列表页跳详情页需要带上整个筛选条件对象更好的做法是把条件对象存到全局数据或storage里详情页从缓存里取。这样页面刷新、分享卡片进来状态也不容易丢。5. 发布审核与上线流程从预览版到正式版的全链路5.1 本地调试、体验版和真机调试开发到一定程度就该上真机看看效果了。官方开发者工具里的模拟器只能代表一部分交互很多问题只有真机能暴露出来比如键盘弹起遮挡输入框、不同屏幕尺寸的适配、内存占用异常。第一步是“预览”工具会生成一个二维码用手机微信扫码就能直接打开当前开发版本。这个版本依赖开发者工具开着适合给身边人快速看效果。第二步是“上传代码”上传成功后在后台把上传版本设为“体验版”然后添加体验成员成员扫码就能体验线上版本即使开发者工具关了也能用。体验版是你在正式发布前最接近真实环境的测试形态。真机调试的时候我建议在手机上打开调试面板也就是常说的 vConsole。它可以在手机屏幕上直接看到 console 日志、网络请求和页面报错。很多问题在电脑上看不出来一旦手机打开调试面板请求的状态码、报错堆栈一目了然。提示体验版和正式版是两套环境有些接口在正式版能用、体验版不能用或者反过来。遇到这类问题先去后台检查“开发环境/生产环境”的配置指向别急着改代码。5.2 提审前的自测清单能少走三次弯路我整理了一份自己每次提审前都会过的检查清单内容不多但每一条都来自真实吃亏经历。第一检查登录流程。审核员第一次打开小程序不会有人工帮你输测试账号。如果你的登录页要求输入手机号、验证码或者依赖某个外部系统请在测试备注里写明完整的操作步骤和测试账号。很多审核被拒的原因不是功能有问题而是审核员根本走不进你的功能页面。第二检查隐私授权。涉及收集用户信息的功能要合理使用官方提供的授权弹窗。不要一启动就弹出一堆授权请求也不要跳过授权直接获取用户信息这些都是常见的拒审原因。第三检查内容类目是否一致。你的小程序叫什么、展示什么内容要和后台选择的类目匹配。比如你是教育类的小程序但页面里全是快递查询功能这会让审核员摸不着头脑大概率会打回让你重新选类目。第四检查支付流程。如果涉及在线支付想办法用测试环境把整个支付、回调、发货流程跑通别让审核员卡在支付环节。第五做一次低版本兼容测试。有些用户的老手机、老版本微信对新的小程序组件支持并不好。条件允许的话用一台低端安卓机走一遍核心流程比什么压测都实在。5.3 发布方式与更新策略审核通过后后台会让你选择发布方式。全量发布是一键对所有用户生效分阶段发布是让一部分用户先看到新版本。我强烈建议只要有条件都选择分阶段发布。原因很简单线上环境你不知道会碰到什么问题。也许你测试时一切正常但真实用户一进来就触发了某个机型专属的 bug。分阶段发布把风险控制在一个小范围内出了问题回滚也容易。代码上线后用户端还会面临一个缓存问题。用户之前打开过旧版本下次启动时可能直接使用旧版本直到微信自动更新。官方提供了wx.getUpdateManager让我们在启动时主动检查更新我一般会在启动页的onLoad里调用它发现有新版本就提示用户重启应用。5.4 运营规范里的几根高压线最后说一点很多人忽略的“运营规范”问题。它虽然不是技术问题但能让你的审核周期从一天变成两周。不要在页面里诱导用户分享比如“分享后才能解锁”“邀请三位好友才可继续”这一类会被判定为诱导分享。不要做标题党小程序名称、页面标题、分享卡片文案要和实际内容一致。不要刷数据不要做虚假交易。更不要碰自己资质范围之外的内容。这些规则写出来很空但一旦触线轻则被打回重则影响账号状态。我自己的经验是提审之前先把自己当成一个最没耐心的用户从头到尾点一遍小程序看哪里会让人困惑、哪里会让审核员找不到功能。这一步比多写一百行代码都管用。6. 上线后的数据观察与迭代节奏不能上线就撒手6.1 用户从哪里来比用户有多少更重要很多人觉得小程序上线就是结束看到“访问人数”往上涨就开心看到掉下来就焦虑。其实单个数字说明不了什么更值得关注的是用户从哪里来。小程序的用户入口有很多聊天分享、扫码、搜索、公众号跳转、固定链接。不同入口带来的用户质量完全不一样。扫码进来的用户多半是线下真实需求搜索进来的用户目的非常明确分享卡片进来的用户可能是被朋友安利也可能是误触。你需要在后台数据里看“访问来源”的分布再决定把运营精力押在哪里。以我自己的一个工具类小程序为例最早天天在群里发分享卡片数据来得很猛但留存很差。后来仔细看来源数据才发现搜索来源的用户虽然量小但次日留存高出分享来源一大截。于是我把重心转到了优化关键词和搜索结果展示上后续的留存曲线明显变稳。这就是数据带来的方向感。6.2 关键指标留存、路径、漏斗对于大多数小程序我建议盯住三个核心指标。第一个是次日留存。小程序本来就是一个用完即走的工具用户第二天愿意主动回来说明你真的解决了他的某个持续需求。次日留存不到百分之十先不要盲目加功能而要想清楚产品到底有没有“回头场景”。第二个是页面访问路径。用户在哪些页面之间流转在哪里停留最久在哪里退出最多能直接暴露产品设计的问题。如果你发现用户从首页进详情页的转化率特别低可能是列表信息没吸引力或者是详情页加载太慢。第三个是核心流程漏斗。以电商为例从浏览商品、加入购物车、提交订单到完成支付每一步都会流失一部分人。把漏斗各环节的转化率拉出来在那一步流失最严重就去优化那一步。这个思路比“用户总数”有用得多。除了平台自带的数据分析你也可以在自己代码里埋点把关键事件上报到自己后端。自己埋点更自由能结合业务细节做分析但要注意控制埋点数量避免采集太多无用信息给用户造成隐私困扰。6.3 版本迭代节奏小步快跑别憋大招上线后的迭代我最想强调的一点是不要憋大招。我见过一些团队上线后开始闷头开发一个“天大的新版本”计划一次性提供十几个全新功能。结果等到发版时老用户打开小程序发现界面全变了找不到原来常用的功能用户流失比增长还快。小程序用户是相当没耐心的群体他们习惯了轻量操作对变化极度敏感。更稳妥的做法是每一到两周上一个版本每次只调整一个核心功能点。改完页面后通过分阶段发布先放给一小部分用户观察数据反馈和用户评价再决定是否全量放量。小程序的技术栈本身适合快速验证不要浪费这种灵活性。6.4 一个始终要提醒自己的问题上线之后还有一个问题会反复出现你做的功能到底是不是用户需要的有一次我上线了一个“消息订阅”功能从技术上做得挺完整但上线两周后点用率非常低。去翻反馈和后台数据才发现用户真正想要的是更快的加载速度和更简单的操作路径而不是再多一个消息通道。那一次我最大的收获是别停留在构建功能的快感里越是技术顺畅的时候越要回到真实用户那里确认方向。做小程序这几年我最大的体会是它不是一个“写代码”的项目而是一个“不断做选择”的项目。选择做什么页面选择怎么处理数据选择上线后优化哪个指标。每一个选择看似都小积累起来就是小程序最后的样子。如果让我给后来者一个建议我会说先做一个能跑通核心流程的最小版本上线看数据再打磨。小程序最好的测试环境是真实用户最好的调试工具是你自己的观察和复盘。把这个节奏跑顺了你离一个“好用的小程序”就不远了。