主题
团队开发与基础封装(二)
本页对应 团队开发与基础封装,共 7 段视频,约 1 小时 8 分。
学习目标
- 能够根据接口文档以 GET 方式封装公告列表请求,依据
code === 10000判断成功并用 toast 提示失败,最终通过wx:for将notice数据渲染到首页且无 key 警告。 - 能够在公告列表跳转链接中拼接
id,在详情页onLoad中解构接收并校验参数,调用详情接口后使用rich-text的nodes渲染含 HTML 标签的content,并通过页面参数与 Network 面板验证。 - 能够定义并全局注册
authorization组件,将其作为页面根节点,通过wx:if控制slot显示,并在attached生命周期依据全局 token 更新isLogin或redirectTo登录页,验证未登录不显示内容且跳转成功。 - 能够使用
count-down组件的time、use-slot和bind:change,结合visible标志控制倒计时显示与隐藏,并在倒计时结束后恢复“获取验证码”,通过缩短时长完成验证。 - 能够使用
field的model:value获取手机号,安装并构建wechat-validate,通过behaviors引入后配置rules并调用validate,依据valid与message控制后续逻辑并提示错误,用空号、错误号、正确号验证。 - 能够以 GET 方式调用
/code接口并携带mobile参数,依据code判断验证码发送结果并提示失败,在 Network 中确认参数与返回验证码,并说明生产环境验证码通过短信下发。
知识点
2.1.1 拉取代码:先拿到“能跑的起点”
为什么需要:这一节不是从零写页面,而是接手一个已经完成静态页面的“享+社区”小程序。笔记里说得很清楚:静态页面已经完成,我们的任务是联调接口、处理业务逻辑。所以第一步不是急着写 wx.http.get,而是先把课程代码拉到本地,保证自己手里有一个和讲师一致的、能编译运行的工程。
怎么想:先分清“已有”和“待做”。已有的是页面结构、样式和静态内容;待做的是把接口返回的数据渲染进去。比如首页公告区域,静态页面里已经摆好了三条公告的布局,但数据还是写死的;联调后要改成接口返回的 notice 数组。先拉代码,就是为了在这个骨架上改,而不是重新搭页面。
怎么做:按笔记给出的仓库地址拉取代码:
bash
笔记里这个代码块没有贴出具体地址,实际以课程页面提供的仓库为准,不要自己猜地址。拉取后把项目目录导入微信开发者工具,选择项目根目录,确认 app.json、页面目录、组件目录都在。
易错点:一是不要把“拉取代码”理解成“新建项目”,否则会丢掉静态页面;二是仓库地址不要照抄空代码块,也不要用来源不明的地址;三是导入后先点一次“编译”,看首页能不能正常显示,再动代码。
启动后先确认项目真的能跑
为什么需要:静态页面能显示,才说明目录、页面路径、组件注册没有大问题。后面联调接口时如果页面白屏,你就很难判断是接口错了还是项目本身没跑起来。
怎么想:先用“最小验证”排除环境问题:能不能编译、首页能不能出现、控制台有没有红色报错。转写稿里讲师在调试公告列表时也强调“重新编译一下”,因为开发者工具里可能残留上一次的请求,重新编译后 Network 里只剩当前 onLoad 发起的那一次请求,观察才准确。
怎么做:编译后打开首页,看公告区域是否有三条静态内容;再打开调试器的 Network 和 AppData。后面联调时,Network 里应能看到公告列表请求,AppData 里能看到 notice 数组,展开后每项包含 id、title、content、createAt 等字段——转写稿中讲师正是这样逐项核对的。
易错点:如果项目使用了 npm 模块,比如后面的 wechat-validate,必须执行“工具 → 构建 npm”,否则模块引入会失败;转写稿里专门提醒“凡是通过 NPM 下载的模块,都得经过构建之后才可以使用”。另外,页面路径要回到 app.json 里核对,不要凭感觉找文件。
从目录看懂后续要改哪里
为什么需要:联调不是漫无目的地翻代码,而是知道“哪个页面对应哪个文件”。转写稿里反复出现几个位置:首页 index、公告详情 notify/index、完善信息 profile、登录页 login/index,以及登录检测组件 components/authorization。这些就是后续工作的地图。
怎么想:把页面和任务对上号:公告列表在首页,公告详情在 notify 页面,登录检测组件挂在 profile 页面作为根节点,验证码逻辑在 login 页面。先记住这个对应关系,后面改代码就不会找错文件。
怎么做:启动项目后,顺着首页点一次公告,观察是否跳转到详情页;再点“完善信息”,看是否加载 authorization 组件。转写稿里讲师就是这样通过开发者工具的页面参数面板确认跳转地址和参数是否带过去的。
易错点:组件要注册。登录检测组件在 app.json 的 usingComponents 里全局注册,页面里才能直接写标签;页面跳转参数要在 onLoad 里接收,参数名要和跳转时拼的一致。
明确本节任务边界:联调接口,不是重做页面
为什么需要:如果不明确边界,很容易把时间花在改样式上。本节的核心是“启动项目”,启动之后才进入公告管理、用户管理这些联调任务。
怎么想:转写稿给出的联调套路很固定:先看接口文档,确认请求方式、路径、参数和响应结构;再封装方法调接口;拿到 code 后判断是否等于 10000,不等于就 return 并 toast 提示;成功则 setData 渲染。公告列表是 GET、无参数;公告详情是 GET、路径带 id;发送验证码是 GET /code,参数是手机号。
怎么做:以公告列表为例,在首页 onLoad 里调用封装好的方法,用 async/await 拿结果,解构出 code 和 data,成功后将 data 改名为有语义的 notice 并通过 setData 渲染;模板里用 wx:for 遍历,记得写 wx:key,否则会有警告。转写稿中讲师删除多余 navigator 后,用 item.title、item.content、item.createAt 填充,页面数据随即变化。
易错点:code 不要写成 code 以外的同音字;async 和 await 要成对出现;setData 不要直接给 this.data.notice 赋值;接口失败时先 return,避免后面的渲染逻辑继续执行。
启动项目的检查清单
拉取代码后按顺序检查:项目能编译、首页能显示、Network 能看到请求、AppData 能看到数据、页面跳转参数能接收。转写稿里登录检测的调试也遵循这个顺序:先在 onLaunch 读取 token 并记录到应用实例,再在组件 attached 生命周期里检测 token,未登录就 redirectTo 登录页,同时用 setData 更新登录状态。启动阶段先把这些位置认全,后面逐节实现时就不会乱。
为什么要做基础封装
小程序项目里,消息提示、网络请求这类能力会在很多页面重复出现。如果每个页面都直接写一遍 wx.showToast、wx.request,代码会重复,参数风格也不统一,后期改起来容易漏改。转写稿里公告列表的实现就是一个典型场景:页面要调接口,接口返回后要先判断 code 是否成功,不成功就提示用户。这里用到的提示方法和请求方法,正适合提前封装成公共模块。基础封装的目标不是“多写一个文件”,而是把通用逻辑收口,让页面代码只关心业务。
消息反馈:把 toast 收口到 utils
为什么需要:wx.showToast 每次都要写 title、mask、icon 等配置,重复且容易漏参数。公告列表里接口失败时,转写稿中直接使用了之前封装好的 toast 方法,并且没有传参数,因为它有默认提示“数据加载失败...”。
怎么想:把提示方法放到 utils/utils.js,既正常导出,又挂到全局对象 wx 上。这样页面里可以按模块导入,也可以直接用 wx.utils.toast(),调用更短。
怎么做:
javascript
// utils/utils.js
const utils = {
/**
* 用户消息反馈
* @param {string} title 文字提示的内容
*/
toast(title = '数据加载失败...') {
wx.showToast({
title,
mask: true,
icon: 'none',
})
}
}
// 挂载到全局对象 wx
wx.utils = utils
// 模块导出
export default utils为了让挂载生效,要在入口中执行这个模块:
javascript
// 在入口中执行 utils.js
import './utils/utils.js'
App({
// ...
})易错点:入口里写 import './utils/utils.js' 是为了执行模块代码,不是导入变量;如果只写模块导出却忘记入口执行,wx.utils 不会自动存在。调用时注意是 wx.utils.toast(),不是 wx.toast()。
全局挂载与模块导出的配合
为什么需要:全局挂载解决“调用方便”,模块导出解决“按需引入、依赖清晰”。两者并不冲突。
怎么想:公共模块先定义对象,再挂 wx,最后 export default。页面里如果已经执行过入口,就可以直接用全局对象;如果某处想显式引入,也可以 import utils from '../../utils/utils'。
怎么做:保持上面 utils.js 的写法即可。转写稿中公告列表失败提示直接调用 toast,说明封装后的方法已经被业务代码复用。
易错点:不要只挂全局就删掉导出;也不要以为 export default 会自动挂到 wx 上。两件事要分别写清楚。
网络请求:为什么还要封装 wx.request
为什么需要:原生 wx.request 不支持返回 Promise,也不支持拦截器。业务里想写 await wx.http.get(...),就必须二次封装,或者使用成熟的第三方模块。转写稿中公告列表写的是 await wx.http.get(...),并配合 async 使用,这说明请求层已经被封装成可 await 的形式。
怎么想:自己从零封装要处理 Promise、基础路径、拦截器、错误处理等;项目里可以直接用 wechat-http,它的用法与 axios 类似:http.baseURL 配基础路径,http.get/post/put/delete 发请求,http.intercept 配拦截器,http 本身也可作为函数调用。
怎么做:先安装并构建 npm:
bash
npm install wechat-http注意安装完成后还必须构建 npm 才能使用。然后新建 utils/http.js:
javascript
// 导入 http 模块
import http from 'wechat-http'
// 基础路径
http.baseURL = 'https://live-api.itheima.net'
// 挂载到全局对象
wx.http = http
// 普通的模块导出
export default http同样在入口执行:
javascript
// 执行 uitls/http.js
import './utils/http.js'
App({
// ...
})易错点:忘记“构建 npm”是最常见问题;baseURL 要在发请求前配置;入口忘记执行 utils/http.js,页面里就没有 wx.http。转写稿里还提醒过:接口返回字段是 code 和 data,不是口语里的“扣子”“date”,判断成功时看 code 是否等于约定值。
用公告列表验证封装是否好用
为什么需要:封装完要回到业务里验证,否则不知道全局挂载、Promise 化、提示方法是否真的可用。
怎么想:在页面里封装 getNotice,用 await wx.http.get 调公告列表接口;拿到结果后解构 code 和 data;如果 code 不等于约定成功值,就 return 并用 toast 提示;成功则把 data 渲染到页面。转写稿中页面加载时在 onLoad 调用,调试时在 Network 看请求是否发出,在 AppData 看 notice 是否拿到三条数据。
怎么做:业务代码保持“先请求、再判 code、失败提示并中断、成功再渲染”的顺序。这里的关键是:await 所在函数要加 async;失败分支要 return,避免继续执行后面的渲染逻辑。
易错点:只判断请求发出、不判断业务 code;失败后忘记 return;把接口返回的 data 写成无意义变量名,后续维护困难。转写稿中把 data 改名为 notice,就是为了语义清晰。
课件图示








代码示例
示例 1:基础封装(toast + http)与公告列表渲染
javascript
/* ---------------- utils/utils.js ---------------- */
const utils = {
/**
* 用户消息反馈
* @param {string} title 文字提示的内容
*/
toast(title = '数据加载失败...') {
wx.showToast({
title,
mask: true,
icon: 'none',
})
}
}
// 挂载到全局对象 wx
wx.utils = utils
// 模块导出
export default utils
/* ---------------- utils/http.js ---------------- */
import http from 'wechat-http'
// 基础路径
http.baseURL = 'https://live-api.itheima.net'
// 挂载到全局对象
wx.http = http
export default http
/* ---------------- app.js ---------------- */
import './utils/utils.js'
import './utils/http.js'
App({
// 全局生命周期...
})
/* ---------------- pages/index/index.js ---------------- */
Page({
data: {
// 先给个空数组,避免 wx:for 渲染报错
notice: []
},
onLoad() {
// 获取公告列表数据
this.getNotice()
},
async getNotice() {
// 以 GET 方式请求公告列表接口(路径以接口文档为准)
const { code, data: notice } = await wx.http.get('/announcement')
// 检测接口是否调用成功
if (code !== 10000) {
wx.utils.toast()
return
}
// 渲染数据:data 改名为 notice,语义更清晰
this.setData({ notice })
}
})xml
<!-- pages/index/index.wxml -->
<view class="notice">
<navigator
wx:for="{{ notice }}"
wx:key="id"
url="/pages/notice/index?id={{ item.id }}"
class="notice-item"
>
<view class="title">{{ item.title }}</view>
<view class="content">{{ item.content }}</view>
<view class="time">{{ item.createdAt }}</view>
</navigator>
</view>关键行与易错点
wx.utils = utils/wx.http = http只是挂载,必须在app.js顶部import './utils/utils.js'、import './utils/http.js'执行一次,否则页面里wx.utils是undefined。wechat-http用npm install wechat-http安装后必须构建 npm 才能使用,漏掉构建会直接报模块找不到。- 解构时
data: notice是重命名,因为data无语义;同时data初始值写成空数组[],否则首次渲染wx:for会拿到undefined。 code !== 10000时先toast再return,提前中断,避免错误数据被setData到页面。wx:for必须配wx:key(这里用item.id),否则控制台会出现警告。- 字段名要对齐接口文档:
title、content、createdAt写错一个,页面就是空白。
示例 2:公告详情(带 id 参数 + rich-text 渲染富文本)
javascript
/* ---------------- pages/index/index.wxml 中的跳转(回顾) ---------------- */
// url="/pages/notice/index?id={{ item.id }}"
/* ---------------- pages/notice/index.js ---------------- */
Page({
data: {
// 接口返回的是对象,初始值给空对象
noticeDetail: {}
},
// onLoad 的参数里就能拿到页面路径上拼的 id
onLoad({ id }) {
// 获取公告详情数据
this.getNoticeDetail(id)
},
// 调用公告详情接口的方法
async getNoticeDetail(id) {
// 检测 id 是否存在,没有 id 就没必要发请求
if (id === undefined) return
// 以 GET 方式请求,路径上拼接 id
const { code, data: noticeDetail } = await wx.http.get('/announcement/' + id)
// 验证接口调用是否成功
if (code !== 10000) {
wx.utils.toast()
return
}
this.setData({ noticeDetail })
}
})xml
<!-- pages/notice/index.wxml -->
<view class="notice-detail">
<view class="title">{{ noticeDetail.title }}</view>
<view class="meta">
<text>{{ noticeDetail.creator.name }}</text>
<text>{{ noticeDetail.createdAt }}</text>
</view>
<!-- content 里带 HTML 标签,小程序不识别,用 rich-text 的 nodes 渲染 -->
<rich-text nodes="{{ noticeDetail.content }}"></rich-text>
</view>关键行与易错点
- 列表页跳转时用
?id={{ item.id }}拼参数,详情页在onLoad({ id })里解构接收,两处名字必须一致。 - 一定要判断
id === undefined再发请求,用户漏传 id 时直接return,否则会请求一个错误地址。 - 接口路径是
/announcement/+ id,注意斜杠别丢、别重复。 - 详情数据是对象,
data初始值写{};写成[]会导致模板取值全部为空。 - 富文本内容带
<p>等 HTML 标签,直接{{ noticeDetail.content }}会原样显示标签,必须用<rich-text nodes="{{ noticeDetail.content }}">。 - 发布人字段是
creator.name(嵌套对象),少写一层会渲染成空。
示例 3:短信验证码(倒计时组件 + 表单校验 + 发送验证码接口)
javascript
/* ---------------- pages/login/index.js ---------------- */
import validate from 'wechat-validate'
Page({
// 通过 behaviors 把校验模块注入页面
behaviors: [validate],
data: {
mobile: '', // 手机号
visible: false, // 是否显示倒计时组件,默认不显示
timeData: {} // 倒计时事件对象里的 detail
},
// wechat-validate 提供的属性,用来定义验证规则
rules: {
mobile: [
{ required: true, message: '请填写手机号码' },
{ pattern: /^1[3-8]\d{9}$/, message: '请填写正确的手机号' }
]
},
// 倒计时时间变化时触发
onCountDownChange(e) {
const { seconds, minutes } = e.detail
this.setData({
timeData: e.detail,
// 秒数大于 0 显示倒计时;0 分 01 秒(刚开始)也不能隐藏
visible: seconds > 0 || minutes === 1
})
},
// 获取短信验证码
async getSMSCode() {
// 获取验证结果
const { valid, message } = this.validate('mobile')
// 验证不合法,则不再执行后面的逻辑
if (!valid) {
return wx.utils.toast(message)
}
// 显示倒计时组件
this.setData({ visible: true })
// 调用接口,把手机号作为参数传给服务端
const { code } = await wx.http.get('/code', {
params: { mobile: this.data.mobile }
})
// 检测验证码是否发送成功
if (code !== 10000) {
return wx.utils.toast('发送失败,稍后重试')
}
}
})xml
<!-- pages/login/index.wxml -->
<view class="form">
<!-- van-field 通过 model:value 双向绑定,拿到输入框里的手机号 -->
<van-field model:value="{{ mobile }}" placeholder="请输入手机号" />
<view wx:if="{{ !visible }}" class="code-btn" bind:tap="getSMSCode">获取验证码</view>
<van-count-down
wx:else
use-slot
time="{{ 60000 }}"
bind:change="onCountDownChange"
>
<text class="count-down-text">{{ timeData.seconds }} 秒后重新获取</text>
</van-count-down>
</view>关键行与易错点
wechat-validate同样要npm install后构建 npm,再用import+behaviors: [validate]注入,否则this.validate不存在。time的单位是毫秒,60 秒要写60000,写成60会一闪而过。- 不写
use-slot时,组件中间自定义的文字不会渲染,只显示组件默认样式。 wx:if和wx:else必须紧挨着,中间不能插入其他节点。- 判断隐藏倒计时不能只写
seconds > 0:60 秒刚开始时 detail 是0 分 00 秒,会被误隐藏,所以要加上|| minutes === 1。 valid为false时必须return,否则倒计时照常启动、接口照常发送。- 手机号正则
/^1[3-8]\d{9}$/只是粗略校验,按实际需求调整;mobile字段名在rules、validate('mobile')、data.mobile三处要保持一致。 - 开发环境接口会把验证码返回用于调试,真实上线时验证码只发到手机短信里,代码不能依赖返回值。
常见错误
1. 页面里调用 wx.http / wx.utils 报 undefined,请求根本发不出去
- 现象:控制台报
Cannot read property 'get' of undefined,Network 面板无任何请求。 - 原因:只在页面里
import了模块,却没有在app.js入口处执行utils/http.js、utils/utils.js,挂载语句(wx.http = http)没跑过;或者 npm 包安装后没执行「工具 → 构建 npm」。 - 修法:
app.js顶部补import './utils/http.js'与import './utils/utils.js';装完wechat-http后必须构建 npm 再重新编译。
2. 公告列表渲染空白,或控制台出现 wx:key 警告
- 现象:模板里
{{item.title}}全空,页面无数据;同时有一条 key 相关 warning。 - 原因:解构响应时把
data直接赋给了notice,但实际列表在data内部字段里;以及wx:for忘了写wx:key="id"。 - 修法:先在 AppData 里确认
notice的真实结构再决定取值层级;wx:for必须补wx:key,用唯一id而不是index。
3. 公告详情页拿不到数据,请求路径里没有 id
- 现象:Network 里请求地址结尾是
/notice/undefined或直接少一段,noticeDetail为undefined。 - 原因:跳转时没在
url后面拼?id=xxx;或详情页onLoad没有接收options参数就往下传。 - 修法:
wx:for里跳转写成url="/pages/notice/index?id={{item.id}}";详情页onLoad(options)解构出id再传给封装方法,方法内部先判空if (!id) return。
4. 公告正文把 <p> 标签原样显示出来
- 现象:页面上出现一堆
<p>、</p>字样而不是排版后的段落。 - 原因:富文本用了普通
text或view插值渲染,小程序不会解析 HTML。 - 修法:改用
rich-text组件,把内容赋给nodes属性(nodes="{{noticeDetail.content}}")。
5. 登录检测组件加载后页面内容整块消失
- 现象:把组件套成根节点后,原来页面里的东西全没了。
- 原因:组件内部既没有
<slot>,或写了wx:if="{{isLogin}}"但isLogin没有在页面data里初始化(值为undefined,条件恒假)。 - 修法:组件模板里补插槽,页面
data中显式初始化isLogin: false,登录成功后再setData置true。
6. 倒计时组件一进页面就自己开始跑 / 跑完不恢复成「获取验证码」
- 现象:还没点按钮,60 秒倒计时已经在动;或归零后按钮文字再也不回来。
- 原因:控制显隐的
visible初始值写成了true;或bind:change里只用seconds === 0判断隐藏,忽略了初始 60 秒时seconds也是 0 的特例。 - 修法:
visible初始false,点击事件里再置true;回调里用「minutes是否为 0 且seconds是否为 0」联合判断,归零才隐藏。
7. 点击「获取验证码」无反应,或校验永远不通过
- 现象:按钮点了没请求、没提示;或明明填了正确手机号仍报错。
- 原因:
behaviors没注册就调this.validate();rules的键名(mobile)与实际传入的字段名不一致;或输入框没写model:value导致data.mobile一直为空。 - 修法:确认
behaviors: [validate]已加在页面配置对象里,rules键名与data字段同名,输入框补双向绑定后再验证。
练习
封装自测:在
app.js中只保留挂载入口、删除页面内的重复 import,验证任意页面都能直接wx.http.get(...)与wx.utils.toast()成功调用;再故意注释掉入口 import,观察报错信息并记录差异。公告列表加固:给
getNotice方法补上「接口失败」与「返回空数组」两种分支处理,空列表时在页面上渲染一行「暂无公告」提示,并说明为什么不能用toast代替空态。详情页参数防御:手动在开发者工具里直接打开详情页(不带
id参数),观察现象;然后改造代码使id缺失时给出提示并返回上一页,写出你的实现思路。倒计时边界测试:把
time依次改成6000、60000、3000,分别测试倒计时结束后按钮能否再次点击;记录哪种取值会触发「秒数为 0 但不该隐藏」的问题,并说明原因。登录检测闭环:在
authorization组件里补充lifetimes中的 token 判断逻辑,验证「未登录 →redirectTo登录页」与「已登录 → 正常展示插槽内容」两条路径;思考为什么这里用redirectTo而不是navigateTo。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 享+生活-公告管理(公告列表) | 09:19 |
| 2 | 享+生活-公告管理(公告详情) | 12:44 |
| 3 | 享+生活-用户管理(登录检测)-1 | 08:14 |
| 4 | 享+生活-用户管理(登录检测)-2 | 07:06 |
| 5 | 享+生活-用户管理(短信验证码)-1 | 11:40 |
| 6 | 享+生活-用户管理(短信验证码)-2 | 12:44 |
| 7 | 享+生活-用户管理(短信验证码)-3 | 06:45 |
小结
- 工具模块的价值在于「一次挂载,处处可用」:挂到
wx上是为了少写 import,但仍需在入口执行一次,否则挂载不会生效。 - 所有接口调用遵循同一套模板:发请求 → 校验
code→ 失败即return并toast→ 成功才setData→ 模板渲染。把这条链路固化成肌肉记忆,比记住某个接口地址更有用。 code !== 10000的提前return是防御式编程的核心:宁可少渲染,也不要让错误数据流进页面。- 字段命名要「去接口化」:把
data改名为notice、noticeDetail这类有语义的名字,是为了让模板可读、让后续维护的人看得懂。 - 页面间传参只有一条路——URL 拼参 +
onLoad接收;参数缺失要在入口处拦住,不要等到请求发出去了才报错。 - 富文本不是普通文本,遇到含标签的内容先想到
rich-text,而不是怀疑数据有问题。 - 组件的
lifetimes是「页面被访问时自动执行」的钩子,登录拦截这类横切逻辑放在组件里,比在每个页面写一遍onLoad判断更省事、更不易漏。 - 倒计时这类交互的本质是「状态控制显隐」,边界值(0 秒、60 秒)永远是 bug 高发区,写完必须用短时长实测一遍。