3步搭建掑簵小程序:从需求分析到上线部署全流程指南
你看到的这段乱码,其实是一个典型的编码转换事故——它原本应该是“微信小程序开发”这六个字,因为字符编码从UTF-8被错误解析成了GBK或者别的什么格式,才变成了这副面目全非的样子。这个现象本身就特别适合拿来引入今天的主题:在微信小程序开发里,编码问题、数据格式、接口交互,往往是新手踩坑最多、又最容易被忽略的地方。
我们先从最基础的开始说。在微信开发者工具里新建项目,第一步就卡住了:AppID填什么?如果你只是个人练习,完全可以选择“测试号”,不需要真的去注册一个企业账号。但如果你打算上线,那就必须去微信公众平台注册一个小程序账号,拿到真正的AppID。这里有个细节容易忽略:注册时选“个人主体”还是“企业主体”,会直接影响你后续能调用哪些接口。比如支付功能、附近的小程序、部分订阅消息,个人主体是开不了的。所以如果你最终目标是做商业项目,哪怕前期先用测试号练手,也要提前规划好主体类型。
一、项目结构里的“隐藏规则”当你打开一个新建的小程序项目,会看到几个默认文件:app.js、app.json、app.wxss,以及一个pages文件夹。很多教程会告诉你“pages里放页面”,但很少有人解释清楚app.json里pages数组的顺序为什么不能乱改。实际上,数组的第一项就是小程序的首页。如果你想把某个页面调整成首页,直接把它移到数组第一位就行,不需要去别的地方配置。另外,app.json里还有一个window字段,用来控制全局的导航栏样式。我见过不少初学者在每个页面的.json文件里重复写导航栏标题,其实完全可以在app.json里设一个默认值,然后在单个页面里覆盖——这样改起来省事得多。
再说一个容易出问题的地方:app.wxss里定义的样式是全局生效的,但如果你在某个页面的.wxss里定义了同名的class,页面的样式会覆盖全局的。这个机制和CSS的层叠规则很像,但有一点不同:小程序里不支持通配符选择器,比如*这种写法是无效的。如果你想让所有页面的字体都变成某个大小,可以在app.wxss里直接给page标签设置样式——page是小程序的根节点,相当于网页里的body。
小程序的模板语法用的是双大括号{{ }},这个和Vue很像,但内部机制完全不同。比如你想根据一个布尔值isShow来控制一段文字的显示隐藏,可以这样写:<view wx:if="{{isShow}}">内容</view>。但要注意,wx:if和hidden的区别:wx:if是真正的条件渲染,如果条件为假,这个节点根本不会出现在DOM里;而hidden只是用display:none隐藏。如果你频繁切换显示状态,用hidden性能更好,因为节点不需要反复创建销毁。反之,如果切换频率很低,用wx:if可以减少初始渲染的压力。
列表渲染也是高频场景。用wx:for循环数组时,一定要加上wx:key,否则控制台会报警告。这个wx:key的值通常是数组里每个对象的唯一标识,比如id。但如果你偷懒用了wx:key="*this",意味着用数组项本身作为key——这只在数组项是字符串或数字时才可靠。如果数组项是对象,*this会导致整个对象被序列化作为key,性能很差且容易出错。所以我建议养成习惯,数组里每个对象都带一个唯一的id字段,哪怕是用时间戳或者随机数生成。
在小程序里绑定点击事件用bindtap,但如果你想传递参数,不能像Web里那样直接写在括号里。比如<view bindtap="handleClick(123)">点我</view>是无效的。正确的做法是用data-*属性:<view data-id="123" bindtap="handleClick">点我</view>,然后在JS里通过event.currentTarget.dataset.id获取。这里有一个踩过的坑:event.currentTarget和event.target的区别。如果你在父元素上绑定了事件,子元素被点击时,event.target指向子元素,而event.currentTarget指向绑定事件的父元素。所以获取自定义属性时,一律用event.currentTarget.dataset,否则在复杂的嵌套结构里很容易拿到错误的值。
另外,事件冒泡也是一个需要注意的点。默认情况下,子元素的bindtap会冒泡到父元素。如果你不想让事件冒泡,用catchtap代替bindtap。这个机制在处理列表项里的按钮时特别有用——比如列表项本身有点击事件跳转详情页,但列表项里的“删除”按钮点击时不应该触发跳转,那么删除按钮就用catchtap绑定事件。
小程序的wx.request接口和浏览器的fetch或axios有很大不同。最明显的一点:请求的URL必须在微信公众平台配置域名白名单,而且必须是HTTPS协议。开发期间可以在开发者工具里勾选“不校验合法域名”,但真机调试时就会报错。很多新手在本地用http://localhost调通后,一上真机就懵了。所以建议你在项目一开始就申请一个HTTPS的测试域名,或者用一些提供HTTPS接口的第三方服务。
还有一个容易被忽略的点:wx.request默认的请求超时时间是60秒,但你可以通过timeout参数自己设置。如果你的接口响应很慢,比如上传图片或者复杂查询,建议把超时时间设长一点,同时配合loading提示给用户反馈。另外,wx.request的success和fail回调里,注意区分statusCode和errMsg。即使HTTP状态码是200,也不代表业务逻辑成功——比如你请求用户信息,服务器返回了200,但data里可能包含一个code: -1表示token过期。所以正确的做法是先判断statusCode是否为200,再判断业务状态码,而不是一进success回调就直接用data。
我见过一个比较极端的例子:有个开发者用wx.request请求一个列表,接口返回的数据里有个字段叫list,但类型是字符串而不是数组——因为后端在空数据时返回了"list": ""而不是"list": []。前端直接用了wx:for去遍历,结果页面空白且没有任何报错。这种问题排查起来很费时间。所以在拿到接口数据后,最好做一层类型校验,比如Array.isArray(res.data.list),如果不是数组就赋一个默认空数组。
小程序的页面生命周期函数有onLoad、onShow、onReady、onHide、onUnload。很多新手搞不清楚onLoad和onShow的区别:onLoad只在页面第一次加载时触发一次,而onShow每次页面显示都会触发。这意味着如果你从A页面跳转到B页面,再返回A页面,A页面的onLoad不会再次执行,但onShow会。所以如果你希望在返回时刷新数据,应该把数据请求逻辑放在onShow里,而不是onLoad里。
页面跳转方式也有讲究。用wx.navigateTo跳转,目标页面会推入页面栈,保留当前页面;用wx.redirectTo会关闭当前页面,跳转到目标页面;用wx.switchTab只能跳转到tabBar页面,并且会关闭所有非tabBar页面。如果你不注意这些区别,很容易出现页面栈堆满导致无法跳转的问题——小程序页面栈最多10层,超过后用navigateTo会失效。所以在深层跳转时,考虑用redirectTo代替navigateTo,或者用getCurrentPages获取页面栈长度,提前做判断。
wx.setStorageSync和wx.getStorageSync是常用的同步存储方法,但不知道存储上限是10MB。如果你存了大量的图片base64数据,很容易超过这个限制。而且小程序被用户手动清除缓存或者卸载重装后,所有存储数据都会消失。所以不要把重要数据只存在本地,比如用户的登录态,最好在服务端也存一份,本地存储只作为加速手段。
还有一个容易被忽略的问题:同步存储方法会阻塞后续代码执行。如果你在onShow里用同步方法读取一个大对象,可能会导致页面渲染卡顿。建议对于大数据量的操作,使用异步版本的wx.setStorage和wx.getStorage,或者把数据拆分成小块。
在开发者工具里看着完美的布局,一到真机上就变形,这是最常见的问题。原因有很多:比如开发者工具的模拟器默认是iPhone 6/7/8的尺寸,但真机屏幕比例多样。解决方法是多用rpx单位——小程序里750rpx等于屏幕宽度,这样在不同屏幕上都按比例缩放。但要注意,rpx不适用于所有场景,比如字体大小用rpx可能会导致在特大屏上字太大,建议字体用px或者用calc动态计算。
还有一个玄学问题:部分Android机型上,position: fixed会失效,尤其是在页面有滚动或者键盘弹出时。解决办法是尽量用flex布局代替fixed定位,或者用scroll-view的scroll-into-view来实现类似效果。如果非要用fixed,可以给父容器加一个transform: translateZ(0)来触发硬件加速,有时候能解决问题。
遇到bug第一反应是“百度一下”,但小程序的很多报错信息比较隐晦。比如页面白屏但控制台没有任何报错,很可能是app.json里注册的页面路径写错了,或者某个组件引用了不存在的文件。这时候可以用vConsole——在真机上打开调试模式,会显示一个悬浮按钮,点开能看到网络请求、日志和系统

