ARTICLE · 1113982
微信小程序实战:从 app.js 到上报页
前端系列最后一篇, 这个微信小程序。它比 uni-app / Vue 更"原始"——没有 npm 构建(原生小程序),用 .wxml/.wxss/.js/.json 四件套。源码体量小,但藏了一个小程序新手必踩的真实结构坑,值得单独讲。
一、小程序骨架:app.js / app.json / pages
全局配置在 app.json 注册页面,app.js 管启动逻辑。pages 数组第一项同样是启动页:
// app.json{"pages": ["pages/index/index", "pages/report/add"],"window": { "navigationBarTitleText": "my-report" }}// app.js —— 启动时调 wx.login 拿 code,发给后端换 openIdApp({onLaunch() {wx.login({success: res => {// 发送 res.code 到后台换取 openId, sessionKey, unionId}})},globalData: { userInfo: null }})
wx.login 拿到的 code 是临时登录凭证,必须马上发给后端用 code2Session 换 openid。源码里 success 回调是空的——别忘了补上请求,否则 openid 永远拿不到(这正好接你写过的 Claw 07 微信免密登录)。
二、上报页:input 组件 + 下划线动画
pages/report/add 是一个带聚焦动画的输入框 + 提交按钮。wxml 用 bindfocus/bindblur/bindinput 绑事件,{{isFocused ? 'active' : ''}} 切换下划线高亮:
<!-- pages/report/add.wxml --><formbindsubmit="formSubmit"><viewclass="input-container"><textclass="input-label">用户名:</text><inputclass="custom-input"name="username"bindfocus="handleFocus" bindblur="handleBlur"bindinput="handleInput" value="{{inputValue}}" /><viewclass="underline {{isFocused ? 'active' : ''}}"></view></view><buttonform-type="submit">提交</button></form>
提交时 formSubmit 拿表单数据,wx.request 发后端:
// pages/report/add.jsformSubmit(e) {const formData = e.detail.valueif (!this.validate(formData)) returnwx.request({url: 'https://your-api.com/submit', // ← 占位地址,记得换成真实接口method: 'POST',data: formData,success(res) { wx.showToast({ title: '提交成功' }) }})},validate(data) {if (!data.username) {wx.showToast({ title: '用户名必填', icon: 'none' })return false}return true}
三、真实结构坑:事件函数放错了位置
源码里 Page() 的选项对象是这样写的:
Page({properties: { /* ... */ },data: {isFocused: false,inputValue: '',handleFocus() { this.setData({ isFocused: true }) }, // ❌ 写在 data 里handleBlur() { /* ... */ }, // ❌ data 里不该放函数handleInput(e) { /* ... */ }},methods: {handleFocus() { /* ... */ }, // ✅ 又写了一遍handleBlur() { /* ... */ },handleInput(e) { /* ... */ }},formSubmit(e) { /* ... */ }})
问题在这:小程序的事件处理函数(handleFocus/handleBlur/handleInput)被同时写进了 data 和 methods。而小程序规定:data 只放响应式数据(字符串/数字/数组/对象),函数必须放在 methods 或顶层。把函数塞进 data,它既不是响应式数据、又不会被当事件处理器注册——等于白写,wxml 里 bindfocus="handleFocus" 找到的是 methods 里那份。
正确写法:data 只留数据,函数全部放 methods(或顶层):
data: { isFocused: false, inputValue: '' },methods: { handleFocus() { this.setData({ isFocused: true }) }, ... }
这个坑很典型:从 Vue 转小程序的人,容易把"data 里既能放数据又能放方法"的直觉带过来,结果调试半天发现聚焦动画不生效。记住——小程序 data 是纯数据,方法去 methods。
四、和后端怎么串:免密登录闭环
这个小程序如果接你之前写的微信免密登录(Claw 07),链路是:
① 小程序 wx.login 拿 code → 发给后端 /public/wechat/get-open-id/{code} 换 openId(对应 uni-app 里的 getOpenId)。
② 前端拼 OAuth2 授权链接带上 wx_openid,跳授权服务器。
③ 后端用 openId 找到用户直接发 token(免密),跳回小程序回调页。
④ 小程序侧把 token 存 wx.setStorageSync,后续 wx.request 的 header 里带上。
注意小程序里 wx.request 默认不带 cookie、也不共享浏览器 token,所以每个请求都得手动塞 Authorization 头,没法像网页那样靠会话自动带。
五、小结
原生小程序的套路就三样:app.json 注册页面、Page() 里 data 放数据/methods 放函数、wxml 用 bind* 绑事件。最容易踩的结构坑就是"把方法写进 data"——这次的源码正好中招。把 wx.login 的 success 补上、占位接口换成真实地址、方法挪出 data,这个上报页就能跑了