这两年微信小程序的开发需求就没断过。不管是创业团队低成本验证产品还是传统企业给线下业务加个线上入口甚至个人想做个工具类应用接点流量小程序几乎都是绕不开的选择。我前前后后做了十几个小程序项目从注册认证、开发者工具配置、技术方案选型到页面开发、提审上架、年审维护整个流程里的坑基本都踩遍了。这篇内容就把微信小程序的开发流程从头到尾捋一遍前期要准备什么、原生开发和uniapp怎么选、项目结构怎么搭、请求缓存登录这些核心功能怎么写、调试发布有哪些环节最后再把我踩过的几个典型问题整理成排查清单。想入门的同学可以直接照着这个流程走有过一定经验的也可以重点看看后面几章的避坑部分。1. 先把地基打好账号注册、认证与开发环境准备1.1 注册小程序账号之前先想清楚主体类型很多新手拿着邮箱就去注册注册到一半发现主体类型选错了要么重新注册一个邮箱要么跟企业对公打款验证纠结很久。注册前一定先明确这个小程序是个人项目还是企业项目。在微信公众平台注册时要选择主体类型。个人主体只需要身份证信息和手机验证流程快但功能权限受限很明显没有微信支付、没有附近的小程序、无法开通部分类目比如电商零售、社交类目的大部分能力只能做展示类、工具类的轻应用。企业主体需要营业执照、法人信息流程里有一个对公账户验证环节微信会给填写的对公账户打一笔随机金额需要财务配合查询后回填整个过程大概1到3个工作日就能完成。企业主体认证费用目前是300元/年认证通过后能开通支付、获取用户手机号等核心能力。这里有个小技巧认证不是必须在注册后立刻做可以先注册一个未认证的企业主体账号拿着AppID先开始开发等快提审的时候补齐认证。但微信支付、手机号授权这些能力必须认证完成后才能用所以如果项目核心功能依赖支付认证流程还是要尽早启动。1.2 开发者工具的选择与首次登录账号注册完成进入小程序后台第一件事是到开发-开发设置页面复制AppID。AppID就是小程序的唯一身份证后面上传代码、真机调试都要用到它和项目密钥要分开放密钥绝不能被写进前端代码。接着去微信官网下载微信开发者工具。这个工具同时承担编译、模拟器、调试器、代码上传四个职责。我习惯直接使用稳定版预发布版虽然能提前体验新能力但碰上编译器级别的bug会影响进度。首次打开需要扫码登录登录的微信必须是小程序管理员或者被加入了项目成员组。第一次创建项目时选择小程序-空模板把AppID粘贴进去项目目录选一个干净的空文件夹语言选JavaScript加WXML加WXSS的原生组合。这一步如果选错了模板类型后面改起来很麻烦所以创建时稍微慢一点看清选项再下一步。1.3 服务器域名明显但总被忽略的坑开发环境里请求随便写都能通因为工具里默认勾选了不校验合法域名。但等到真机预览或者提交审核的时候如果请求的域名不在小程序后台的合法域名列表里所有网络请求会直接fail。这个坑我见太多人踩过了。生产环境的域名要求很明确必须是HTTPS域名需要完成ICP备案证书要有效且信任链完整。在小程序后台的开发管理-开发设置-服务器域名中把request合法域名、uploadFile合法域名、downloadFile合法域名分别配置好。注意一个细节合法域名不能带端口且不能使用IP。如果你后端服务之前是用IP加端口暴露的这一步就要把服务和域名绑好Nginx反向代理、SSL证书都提前配好。我们团队的做法是在项目里把接口域名统一放到常量文件里开发环境用工具的不校验域名开关到提测前才切成线上域名避免一把域名写死到处改。2. 技术选型原生、uniapp还是Taro2.1 三种方案的基本差异拿到新项目很多团队第一个问的就是用原生还是uniapp。这个问题没有标准答案但选错方案后期改造成本极高。原生小程序是最正统的方式代码分四件套WXML负责结构、WXSS负责样式、JS负责逻辑、JSON负责配置。它的优势是性能最好官方新能力比如最新的渲染引擎、Component2组件体系基本只保证原生项目第一时间可用调试最直接编译出来就是真正的运行代码。缺点是语法体系比较封闭和Web开发习惯差别大而且只支持微信一个平台。uniapp是目前社区最活跃的跨端方案核心思路是写一套基于Vue语法的代码编译到微信小程序、H5、App等多个端。对已经有Vue经验的团队来说学习曲线很低而且生态里有很多现成组件。我在一个多端需求的项目里就用uniapp做过一轮体验是日常页面开发效率和原生差不多但遇到平台差异化需求比如微信支付和App的支付流程完全不同就要写条件编译调试排错时也多一层编译前源码和编译后产物不一致的心智负担。Taro则是React语法路线适合强React技术栈的团队使用。整体来说uniapp和Taro的核心价值都在跨端复用而不是能写出更牛的代码。我来做一张对照表方便参考维度原生小程序uniappTaro开发语言JS WXML/WXSSVue语法React语法跨端能力仅微信微信/H5/App/鸿蒙微信/H5/App性能最优中等编译后略有损耗中等上手成本较低但语法特殊对Vue用户很低对React用户很低社区生态官方组件 第三方插件uView、uni-ui等丰富Taro UI等较丰富最适合场景只做微信端、追求体验需要多端、团队会Vue团队React栈、需要多端2.2 我的选型建议别一上来就冲着某套框架去先确认需求边界项目只做微信端还是以后肯定要上App和H5如果是前者原生小程序就是最省事的选择维护链路短、问题直观、性能不打折扣。如果已经有明确的多端规划不用犹豫选uniapp。但无论选哪套我强烈建议一个项目里只保留一套技术栈。见过有些团队先写了一段原生WXML又引了uniapp组件最后只能把原生代码全部重写这种返工其实可以从一开始就避免。如果项目是微信小程序游戏那另外单独说一句小游戏走的是另一套开发体系用的小游戏引擎比如Cocos、LayaAir和普通小程序的组件体系完全不同后续上架审核还涉及软著、版号等要求别跟普通小程序的开发流程混在一起。游戏类目的审核标准和功能限制也比普通小程序严格得多立项之前务必先查清楚目标类目的准入条件。3. 项目搭建与目录结构从模板到可维护3.1 原生项目的目录骨架我习惯把原生小程序项目的目录按职责划分初期结构越清晰后期加页面、加组件时越不费力。一个典型的项目骨架类似这样├── app.js // 小程序入口全局生命周期、全局变量 ├── app.json // 全局配置页面注册、窗口样式、tabBar、超时 ├── app.wxss // 全局样式 ├── project.config.json // 项目配置AppID、编译设置、ES6转ES5 ├── sitemap.json // 搜索索引配置控制页面是否被微信收录 ├── pages/ │ ├── index/ // 每个页面有4个文件 │ │ ├── index.js │ │ ├── index.wxml │ │ ├── index.wxss │ │ └── index.json │ ├── list/ │ └── detail/ ├── components/ // 自定义组件按使用场景分子目录 ├── utils/ // 请求封装、缓存封装、工具函数 └── images/新手最容易混淆的是app.json和页面json的职责app.json里的window配置是全局默认值页面json里的window类配置只覆盖当前页。比如首页想用红色导航栏其他页用绿色导航栏就可以在全局配绿色、在首页json里配红色这个覆盖关系是开发中很常用的能力。3.2 tabBar配置的细节底部导航栏是工具类小程序最常见的交互形态。app.json里的tabBar需要配置一个list数组最少2个、最多5个。每个tab项必须有pagePath和text如果带了图标iconPath和selectedIconPath要成对出现。tabBar图标有几个容易踩的细节图标文件大小不能超过40KB、尺寸建议81x81像素格式可以是png或jpg但不支持网络图片。更要紧的是使用tabBar的页面路径必须已经声明在pages数组里否则会报错找不到页面。另外tabBar页面的跳转只能用wx.switchTab不能用wx.navigateTo这两个API的区别很多新手会弄混导致的报错信息也不算友好。3.3 页面生命周期决定数据加载时机小程序页面的生命周期顺序是onLoad - onShow - onReady - onHide离开时触发。下拉刷新触发onPullDownRefresh页面滚动到底部触发onReachBottom右上角菜单的分享点击触发onShareAppMessage。很多数据加载的bug都和生命周期选错有关。我的经验是首次进入初始化数据放在onLoad里页面参数也在这里通过options拿到这些数据在页面整个存活期间有效每次从后台切回、从别的页面返回时需要刷新数据要放在onShow里因为onLoad只触发一次下拉刷新里重新拉数据结束后记得调用wx.stopPullDownRefresh()否则刷新状态的loading动画会一直转。onReachBottom触底加载是列表页的核心逻辑。要注意它是触发一次拉一次的机制如果请求还没回来用户又滑了一次就会重复请求。常规做法是加一个isLoading的开关进入函数先判断true就直接return请求完成再设为false。实际项目里我还会把pageNo和hasMore也做成页面的data字段和isLoading一起维护这样触底加载的代码整体都很清晰。4. 功能开发里的关键环节请求、登录、缓存和导航栏4.1 请求封装的正确姿势原生小程序的网络请求是wx.request直接裸用会面临几个问题每个页面都要写完整url、每个接口都要处理Loading和错误提示、token过期没有统一处理。所以封装一层HTTP客户端几乎成了必需。我项目里的封装逻辑大致是统一前缀、自动携带token、统一错误码处理、支持Promise回调。核心代码类似这样// utils/request.js const BASE_URL https://api.example.com function request(path, method, data, options {}) { const token wx.getStorageSync(token) return new Promise((resolve, reject) { wx.request({ url: BASE_URL path, method: method, data: data, header: { Content-Type: application/json, Authorization: token ? Bearer ${token} : }, timeout: 10000, success(res) { if (res.statusCode 200) { resolve(res.data) } else if (res.statusCode 401) { wx.removeStorageSync(token) wx.navigateTo({ url: /pages/login/login }) reject({ code: 401, message: 登录已过期 }) } else { wx.showToast({ title: 请求失败请稍后重试, icon: none }) reject(res) } }, fail(err) { wx.showToast({ title: 网络异常, icon: none }) reject(err) } }) }) } module.exports { get(path, data) { return request(path, GET, data) }, post(path, data) { return request(path, POST, data) } }封装好后页面里的调用就变得很干净const api require(../../utils/request.js) api.get(/products, { page: 1 }) .then(res { this.setData({ list: res.data.list }) }) .catch(err { console.error(请求失败, err) })这个封装的精髓在于token的读取和失效处理都集中在同一个地方页面代码不关心这些细节。后续如果要接埋点、做请求日志也只需要改封装那一个文件。还有个容易被忽略的点是wx.request在请求中如果遇到网络切换fail回调里拿到的err包含errMsg可以按errMsg里的关键词区分是超时、断网还是域名问题在fail回调里统一弹出更友好的提示。4.2 登录态与用户授权的最新姿势小程序的登录体系和传统Web差距很大。核心是wx.login拿到临时code把code发给后端后端调用微信接口换到openid和session_key。openid是用户在小程序下的唯一标识后端拿到它就可以建立自己的用户表。用户资料这里的坑最密集。以前直接wx.getUserInfo就能拿到头像昵称后来微信隐私收紧从某个版本开始getUserInfo返回的都是默认值。现在官方推荐的做法是头像昵称填写能力用一个buttonopen-typechooseAvatar让用户选择头像用一个input让用户填昵称。注意chooseAvatar不在默认开放范围里需要在小程序后台的隐私保护指引中声明使用此接口否则真机上一调用就报api scope is not declared之类的错误。这个报错在常见问题章节里我还会详细展开。获取手机号也是常用能力企业认证的小程序里button open-typegetPhoneNumber配合后端接口可以拿到手机号个人主体直接没有这个能力。做登录页的时候我建议把登录态做成静默优先先尝试用之前缓存的token访问一个轻量接口比如获取用户信息成功了就直接进入主页只有token失效或首次进入时才弹出登录页这样能显著降低用户进入成本。4.3 缓存与过期时间别让数据永远不过期wx.setStorageSync和wx.getStorageSync是同步版本读写方便但要记住整体存储上限是10MB。缓存设计里一个常见的坑是写入容易判断缓存是否过期却没有现成API时间字段要自己拼。我在utils里放了这样一套缓存工具function setCache(key, value, expireSeconds) { wx.setStorageSync(key, { value: value, expire: Date.now() expireSeconds * 1000 }) } function getCache(key) { const data wx.getStorageSync(key) if (!data) return null if (Date.now() data.expire) { wx.removeStorageSync(key) return null } return data.value }setCache(home_list, list, 60 * 60)调用起来home_list缓存1小时有效。页面优先读缓存缓存过期再去请求接口这样既能秒开页面又不会出现昨天的数据还在的尴尬。日常开发中首页列表、用户资料这类数据都适合加缓存但是订单状态、支付结果这类强实时数据千万别缓存宁可每次请求也不要用旧数据误导用户。4.4 顶部导航栏高度自定义导航的适配公式原生的导航栏虽然省事但样式很受限想做沉浸式背景或者自定义按钮就必须在页面json里设置navigationStyle为custom。改成自定义之后所有页面内容顶到屏幕最上方就必须要自己算安全区高度否则按钮会被状态栏和胶囊按钮遮挡。适配公式用起来很顺手核心是拿到状态栏高度和胶囊按钮的位置const systemInfo wx.getSystemInfoSync() const menuRect wx.getMenuButtonBoundingClientRect() const statusBarHeight systemInfo.statusBarHeight // 导航栏总高度胶囊上下对称延伸后的高度 const navBarHeight (menuRect.top - statusBarHeight) * 2 menuRect.height理解这个公式比背下来更重要胶囊按钮距离状态栏顶部的间距乘2是因为胶囊上方和下方要留同样的间距再加胶囊自身高度正好就是自定义导航栏占的高度。不同机型胶囊位置不同所以这个值必须运行时计算不能写死。开发的时候建议在导航栏组件里加一个调试模式把statusBarHeight和menuRect实时打出来拿到真机上对比不同机型数字会有明显差异。适配做完之后再把整个页面用一种浅色背景测试一遍因为导航栏和状态栏都用了自定义样式深色背景下状态栏文字颜色也要配套调整。5. 调试、测试和发布上线的完整链路5.1 开发者工具里的调试效率技巧开发者工具里最常用的几个功能使用顺序其实固定。日常开发用编译模式工具会热重载改动文件后页面自动重新编译。这个热刷新只存在于开发工具里线上小程序根本没有热更新机制。有些团队把线上能不能热刷新覆盖bug当成默认能力这是个认知误区小程序的线上更新必须走完整的提审发布流程。调试页面样式时我习惯用调试器的Wxml面板。在这个面板里能直接看到渲染出来的页面节点树可以临时改WXML里的文本、给节点加class、调整样式改动实时反映在模拟器上。对排查为什么这个样式没生效类的问题特别高效。Network面板可以看每个请求的状态码、耗时和返回体比在代码里console.log省事得多。开发者工具还有个清缓存按钮可以一键清掉网络缓存、数据缓存、文件缓存。遇到我明明改了代码怎么效果没变化的灵异事件先清一次缓存再重新编译大多数情况能解决。5.2 真机预览与真机调试的区别模拟器不等于真机这一点必须反复强调。很多模拟器上看起来正常的页面到了真机就出问题比如摄像头调用、定位授权、支付跳转、字体渲染差异。所以除非只是改界面文案我都会要求至少做一次真机验证。预览是把当前编译结果生成一个二维码手机微信扫码后可以直接打开小程序项目适合快速给同事看效果。注意预览码有时效性过期要重新生成。真机调试则是手机和电脑建立调试通道手机上的操作会实时同步日志到电脑的调试器断点调试、看console、看Network都行排查真机问题主要靠这个。需要出门验证支付时真机调试里可以设置不校验合法域名这样开发环境接口也能在真机测通但发布前一定要切回来。如果你做的是蓝牙、地图这类强依赖硬件能力的功能真机调试基本是唯一可靠的验证方式。5.3 体验版、提交审核与正式发布开发自测完成后在开发者工具右上角点上传填一个版本号比如1.0.0和改动说明。这个版本号建议和后端版本发布记录保持同步方便测试人员比对。上传完之后到小程序管理后台的版本管理页面可以看到刚才上传的版本。把它设为体验版再在成员管理里添加体验成员体验成员用微信扫码就能打开体验版小程序。体验版是小程序和正式版之间的中间态适合给产品、测试、老板做验收。注意体验版不涉及审核改动后重新上传即可。验收通过后在版本管理里把体验版变成提交审核需要填版本描述和功能页面截图。审核时间通常1到7天偶尔更快。审核不过的常见原因我后面会整理。审核通过后点发布按钮正式版上线。发布之后还可以用灰度发布功能按比例把新版本推给部分用户跑几天没问题再全量放开这个功能很适合有风险的大版本更新。有几个细节小程序提审时填写的功能页面截图会影响审核效率尽量上传真实页面截图不要用占位图涉及支付的类目、涉及医疗健康的类目审核标准完全不一样开发前先确认类目是否匹配。审核人员会实际点开每个主要页面如果发现某个按钮点击后没有响应基本会被驳回。5.4 年审与长期维护小程序不是上线就一劳永逸。企业主体的小程序每年要做一次年审费用同样是300元左右。如果因为疏忽没去年审小程序会被限制使用所有用户打开都会看到该小程序已暂停服务处理起来很被动。运营过程中如果改了服务内容、新增了类目也需要重新审核。所以我把年审这件事放在团队日历里提前一个月提醒别等到最后一周才去处理。发布后的日常迭代走的依然是开发-上传-体验版-提交审核-发布这条链路。只是后期提交的改动说明要写清楚影响范围审核人员也会根据变更内容决定是否做功能测试。线上问题如果很紧急可以先发一版撤回上一个版本回到稳定状态再慢慢修复。这个操作在小程序后台的版本管理里可以直接点比干等着改代码靠谱得多。6. 高频问题排查清单与避坑经验6.1 隐私接口未声明报错的处理现在真机上调用chooseAvatar、getLocation、chooseMedia这类涉及用户隐私的接口如果小程序后台的隐私保护指引里没有声明命令行会报类似api scope is not declared的错误。这个问题的处理路径是进入小程序后台在设置-服务内容声明-用户隐私保护指引中补充使用的隐私接口微信会生成隐私协议文本需要确认并提交审核。注意这个操作修改后也可能需要重新提审版本才会生效所以建议在项目启动初期就把隐私声明做全别等到开发到一半才处理。另外隐私协议文本要写得清晰别把一堆接口堆在一起避免审核人员要求拆分。开发阶段如果为了先跑通功能可以在工具里临时关闭隐私弹窗检查但上线前必须恢复。6.2 iOS真机网络请求失败率高的常见原因这个痛点在开发者社区里出现频率很高。项目热词里也有iOS机型网络请求失败的率很高这样真实的线上反馈这个问题的背后通常是几个原因叠加生产环境用的是HTTP而非HTTPSAndroid对明文请求容忍度稍高iOS在ATS策略下直接拦截HTTPS证书的有效性不完整比如中间证书缺失、证书过期iOS校验比Android严格会报证书相关错误请求URL带了不常见的端口虽然合法域名不能带端口但开发阶段有人手动绕过校验真机上就会翻车请求并发数过高、超时设置过短iOS网络栈对并发连接数更敏感。排查建议是先在iPhone的真机调试里看Network面板中失败请求的具体error信息区分是证书错误、域名拦截还是超时。生产环境必须保证HTTPS且证书信任链完整超时时间设到10到15秒并发多的场景在前端做请求合并或缓存。如果你用的是自签名证书做联调那在iOS真机上一定过不了必须换正式的开发环境证书。6.3 包体积超限与分包方案小程序包体积有严格限制主包和单个分包不能超过2MB所有包加起来不能超过30MB左右具体限制以官方文档为准。包体超限最常由以下原因引起把图片作为静态资源打包、引用了体积过大的UI库、SDK整包集成。解决方案有三个层面一是用subpackages把低频页面拆到分包里比如商家后台、活动专题页二是体积大的第三方库按需引入不要整包import三是静态图片放到CDN代码里只保留图标类的小资源。排查包体积可以在开发者工具的详情-基本信息中看到主包和各分包的大小逐项确认来源。这里有个体验上的细节分包启动时主包里的公共代码仍然会先加载所以不要把公共库全放主包尽量把通用逻辑做成小模块避免主包启动太慢。6.4 H5页唤起小程序的常见失败原因微信里H5页面可以通过wx-open-launch-weapp标签唤起同一公众号关联的小程序。常见的链接无法访问原因大多集中在这几个点公众号没有和小程序做关联没有配置JS接口安全域名页面的微信JS-SDK配置不正确使用环境不是微信内置浏览器普通浏览器里这个标签不生效。处理顺序就是后台绑定公众号和小程序关联、配置JS安全域名、用wx.config完成权限注入、在页面中放置开放标签再实测。还要注意标签内嵌的跳转链接域名必须和当前页面域名一致否则同样会报错。这个功能上线前要多拿几台手机测不同微信版本的开放标签行为有些差异。6.5 单选框、复选框自定义样式的通用做法原生radio和checkbox的样式定制限制很大很多设计稿里的选中态、圆角、动画在原生组件上做不出来。我的做法是直接不用原生组件用view加active类模拟。列表里维护一个选中的索引点击时setData更新索引样式上用class切换。数据量不大时这个方案灵活性和兼容性都比原生radio好尤其是当选项需要展示多行文本、副标题甚至缩略图时原生组件的布局能力完全不够用。模拟单选框的代码不复杂核心思路是索引即状态// data里维护 selectedIndex默认 -1 // wxml里用外层的data-index记录当前项 // 点击时 setData({ selectedIndex: index })配合wxss里的active类切换选中态视觉上完全能复刻设计稿。这个方案的隐性好处是后续如果要把单选改成多选数据结构的调整也很方便不需要动模板结构。写到这里基本把小程序开发的全流程都过了一遍。如果只让我给一个建议那就是别急着写代码先把账号、域名、主体类型这些地基问题确认清楚。我在实际项目中耽误最多时间的从来不是代码逻辑而是AppID拿错、域名没备案、隐私接口没声明这类前置问题。前端开发可以快速迭代但底层准备一次性做扎实整个项目才能跑得顺。最后分享一个小技巧把注册信息、AppID、服务器域名、隐私声明清单、年审时间做成一份项目初始化checklist每次新项目直接照着确认能帮你省掉大量来回沟通的时间。