主题
生命周期、分包与自定义组件(三)
本页对应 生命周期、分包与自定义组件,共 7 段视频,约 49 分钟。
学习目标
- 能够在
app.json中配置lazyCodeLoading: requiredComponents,并通过代码质量扫描验证组件按需注入生效。 - 能够查阅 Vant 组件文档,完成导航栏组件的全局注册,并使用其属性、插槽和事件实现页面导航功能。
- 能够通过审查元素定位 Vant 组件内部类名,覆盖标题、图标及按下态样式,并正确使用
!important、伪元素和custom-class。 - 能够在
app.wxss根选择器中定义 CSS 变量作为全局主题色,并使用var()应用变量,验证修改一处后全局样式同步变化。 - 能够使用
getApp()获取应用实例并读写全局数据,使用getCurrentPages()获取页面栈、取最后一项作为当前页面实例并读取其route。 - 能够根据业务场景选择
onLoad或onShow,使用本地存储 API 读取图书列表并通过setData渲染到页面。
知识点
3.1 昵称和头像
为什么需要: 用户资料如果让用户手填,体验差、容易填错。小程序提供了昵称和头像的快捷获取方式,适合做个人资料页。
怎么想: 昵称用 input 的 type="nickname",在 blur 时取 ev.detail;头像用 button 的 open-type="chooseAvatar",监听 chooseavatar 事件,从 ev.detail.avatarUrl 取临时地址。
怎么做: 先保留本节示例结构:
xml
<view class="profile">
<view class="nickname">
<label for="">昵称:</label>
<input type="text" />
</view>
<view class="avatar">
<label for="">头像:</label>
<button>
<image src="" mode="aspectFill"/>
</button>
</view>
</view>css
.profile {
min-height: 300rpx;
padding: 30rpx;
border-radius: 10rpx;
background-color: #fff;
}
.nickname, .avatar {
display: flex;
align-items: center;
height: 80rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.nickname label, .avatar label {
margin-right: 20rpx;
}
.nickname input {
flex: 1;
border: none;
text-align: right;
}
.avatar button {
flex: 1;
height: 60rpx;
padding: 0 20rpx;
text-align: right;
background-color: transparent;
}
.avatar image {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
vertical-align: baseline;
background-image: url(https://lotjol.github.io/static-store/enjoy-plus/images/user_placeholder.png);
background-size: cover;
}在上面的结构上做两处关键设置:input 改为 type="nickname" 并绑定 blur;button 加 open-type="chooseAvatar" 并绑定 chooseavatar。回调里分别取 ev.detail.nickname、ev.detail.avatarUrl,再用 setData 显示。
易错点: 事件名是 chooseavatar,没有大写字母;开发者工具里 blur 有时需要失去焦点两次才触发;昵称不是从 ev.target.value 取,而是从 ev.detail 取;头像拿到的是临时路径,后续按业务需要上传或保存。
3.2 应用实例
为什么需要: 多个页面可能需要共享 token、版本号、用户信息或公共方法。每个页面各存一份会重复且难同步,因此需要全局唯一的应用实例。
怎么想: app.js 中调用 App() 会注册应用实例,全局唯一;任意页面通过 getApp() 获取。共享数据可放 globalData,也可以直接挂在实例上,但 globalData 是更清晰的规范。
怎么做:
javascript
App({
message: '应用实例中的数据...',
updateMessage() {
// this 指向应用实例本身
}
})javascript
// 获取用应实例
const app = getApp()
Page({
onLoad() {
// 查看应用实例中的内容
console.log(app)
}
})转写稿中的调试过程是:在 app.js 的 globalData 里放 token,在页面中 getApp() 后打印,能看到实例上的数据、生命周期和自定义方法;也可以在 App() 外层直接写 version,通过 app.version 读取。修改时先 const app = getApp(),再 app.globalData.token = '...',刷新后打印可看到新值。
易错点: this 在 App() 方法里指向应用实例;globalData 只是约定,不是强制;不要把应用实例和页面 data 混用;修改全局数据后,已显示的页面不会自动更新,需要页面自己 setData。
3.3 页面栈
为什么需要: 有时要拿当前页面的实例、路由、参数,或调用页面上的 setData、生命周期。页面栈记录了当前打开过的页面历史,能帮我们定位当前页面。
怎么想: 全局调用 getCurrentPages() 得到数组,每个单元是一个页面实例。数组最后一项才是当前页面;TabBar 页切换时,switchTab 会关闭其他非 Tab 页,所以 Tab 页里通常只有一项;普通页面跳转会继续往数组后面追加。
怎么做: 在控制台或页面方法中打印页面栈,观察 data、setData、onShow、onLoad、route 等信息。取当前页面实例时:
javascript
const pages = getCurrentPages()
const currentPage = pages[pages.length - 1]
console.log(currentPage.route)转写稿中的调试是:在 Tab 页打印只有一项;跳到非 Tab 的 test 页面后再打印,数组变成两项,当前页在最后;绑定按钮取 length - 1,能拿到当前页面实例并打印 route。
易错点: 函数是 getCurrentPages,不是单数;不要默认第一项是当前页;Tab 页数组通常只有一项;页面实例不要长期缓存。生命周期选择也要看业务:查询只在首次加载可用 onLoad,购物车等每次显示都要最新数据则用 onShow。
课件图示








代码示例
1. 获取用户昵称与头像(input + button)
WXML(pages/profile/profile.wxml)
xml
<view class="profile">
<view class="nickname">
<label for="">昵称:</label>
<input
type="nickname"
value="{{nickName}}"
placeholder="请输入昵称"
bindblur="onNicknameBlur"
/>
</view>
<view class="avatar">
<label for="">头像:</label>
<button open-type="chooseAvatar" bindchooseavatar="onChooseAvatar">
<image src="{{avatarUrl}}" mode="aspectFill" />
</button>
</view>
</view>WXSS(pages/profile/profile.wxss)
css
.profile {
min-height: 300rpx;
padding: 30rpx;
border-radius: 10rpx;
background-color: #fff;
}
.nickname, .avatar {
display: flex;
align-items: center;
height: 80rpx;
padding: 0 10rpx;
border-bottom: 1rpx solid #eee;
}
.nickname label, .avatar label {
margin-right: 20rpx;
}
.nickname input {
flex: 1;
border: none;
text-align: right;
}
.avatar button {
flex: 1;
height: 60rpx;
padding: 0 20rpx;
text-align: right;
background-color: transparent;
}
.avatar image {
width: 60rpx;
height: 60rpx;
border-radius: 50%;
vertical-align: baseline;
background-image: url(https://lotjol.github.io/static-store/enjoy-plus/images/user_placeholder.png);
background-size: cover;
}JS(pages/profile/profile.js)
js
Page({
data: {
nickName: '',
avatarUrl: ''
},
// 昵称:input 失去焦点时,从 ev.detail 中取
onNicknameBlur(ev) {
console.log(ev.detail)
this.setData({ nickName: ev.detail.value })
},
// 头像:chooseavatar 事件(全小写),从 ev.detail.avatarUrl 中取
onChooseAvatar(ev) {
console.log(ev.detail)
this.setData({ avatarUrl: ev.detail.avatarUrl })
}
})要点解释 / 易错点
input的type必须是nickname,才会出现微信昵称的快捷填写。- 昵称走的是
blur事件,在开发者工具里需要失焦两次才会触发,真机正常。 - 头像按钮的
open-type="chooseAvatar",事件名是bindchooseavatar——全部小写,没有大写字母,写成bindChooseAvatar不生效。 - 两者数据都从
ev.detail里取:昵称是ev.detail.value,头像是ev.detail.avatarUrl。 - 图片用
mode="aspectFill"做圆形裁切,占位图放在background-image里。
2. 应用实例 getApp + 按需注入配置
app.json
json
{
"pages": [
"pages/index/index"
],
"lazyCodeLoading": "requiredComponents"
}app.js
js
App({
globalData: {
token: ''
},
version: '1.0.0',
message: '应用实例中的数据...',
updateMessage() {
// this 指向应用实例本身
}
})页面 JS(pages/index/index.js)
js
// 获取应用实例(全局唯一)
const app = getApp()
Page({
onLoad() {
// 查看应用实例中的内容
console.log(app)
console.log(app.globalData.token)
console.log(app.version)
},
setToken() {
// 直接赋值即可修改全局数据
app.globalData.token = 'ABCDE'
console.log(getApp().globalData.token)
}
})页面 WXML(pages/index/index.wxml)
xml
<button type="primary" bindtap="setToken">全局设置</button>要点解释 / 易错点
App()注册的应用实例全局唯一,任意页面用getApp()拿到的都是同一个对象。globalData只是书写规范,不是强制容器;像version直接挂在实例上也一样能通过app.version取到。- 修改全局数据不需要
setData,直接app.globalData.token = xxx赋值即可;但要刷新页面数据仍需页面自己setData。 - 不要在
app.js里调用getApp(),那时实例还没注册完,取到的是undefined。 lazyCodeLoading的值只有"requiredComponents"一种写法(懒加载,用到哪个组件才注入哪个),加到app.json里就能消掉代码质量报告里的「按需注入」未通过项。
3. 页面栈 getCurrentPages + 生命周期 + 本地存储
页面 JS(pages/books/books.js)
js
Page({
data: {
list: []
},
// 加载时把本地存储读出来,回显到页面
onLoad() {
const list = wx.getStorageSync('list')
if (list) {
this.setData({ list })
}
},
// 查询书单并写入本地存储
getBooks() {
// 模拟请求返回:res.data.data 是书单数组
const res = { data: { data: ['书1', '书2', '书3'] } }
this.setData({ list: res.data.data })
wx.setStorageSync('list', res.data.data)
},
// 取当前页面实例
getCurrentPage() {
const pages = getCurrentPages() // 页面栈,是一个数组
const current = pages[pages.length - 1] // 最后一项才是当前页面
console.log(current) // 页面实例
console.log(current.data) // 页面的初始数据
console.log(current.route) // 页面路径
}
})页面 WXML(pages/books/books.wxml)
xml
<button type="primary" bindtap="getBooks">查询书单</button>
<button bindtap="getCurrentPage">获取当前页面实例</button>
<view wx:for="{{list}}" wx:key="*this">{{item}}</view>要点解释 / 易错点
getCurrentPages()是全局函数,返回的是数组,数组最后一项才是当前页面,取pages[pages.length - 1]。- tabBar 页面之间切换,
wx.switchTab会关闭其他非 tabBar 页面,所以页面栈里永远只有一项;普通navigateTo跳转才会往后追加。 onLoad只在页面首次加载触发一次,适合只读一次本地缓存;像购物车这种每次显示都要拿最新数据的,要用onShow。wx.setStorageSync的第二个参数要传真正要存的数据(这里是res.data.data),不是整个res,也不是this.data整个对象。- 页面实例上能直接看到
data、setData、onLoad/onShow等生命周期、route等,借助页面栈就能跨页面拿这些信息。
常见错误
- 昵称输入后取不到值 → 监听成了
input事件或未触发失焦 → 用type="nickname",监听blur,从ev.detail取值;开发者工具中失焦两次再测。 - 头像按钮点击无反应 →
open-type或事件名大小写写错 → 写open-type="chooseAvatar",监听全小写chooseavatar,取ev.detail.avatarUrl。 - 用
getApp()改了全局数据,页面仍显示旧值 → 把全局对象当成响应式数据 → 修改后手动setData,或在使用页面重新读取最新值。 - 用
getCurrentPages()取当前页取错 → 取了pages[0],或忽略 tabBar 切页会清历史栈 → 取pages[pages.length - 1];tabBar 场景只按当前页处理。 - 给 Vant 组件直接写
class改样式无效 → 组件样式隔离、权重不足,字体图标还需命中伪元素 → 查文档用外部样式类/custom-class,审查元素后补权重;图标补::before,按下白块覆盖hover-class。 - 代码质量报告仍提示组件未按需注入/图片超 200K → 配置位置写错,或只做分包没换大图 →
app.json配"lazyCodeLoading": "requiredComponents";大图改网络资源并删本地大图。
练习
- 完成昵称与头像采集:输入昵称后失焦取值,点击头像按钮选择头像并回显,处理开发者工具需失焦两次的情况。
- 在
app.js中维护token,在页面按钮点击时通过getApp()修改它,再在另一个页面读取展示;说明页面展示是否需要额外setData。 - 用
getCurrentPages()打印页面栈,分别验证普通页面跳转与 tabBar 切换后的数组长度、最后一项route。 - 用 Vant 导航栏替换自定义导航栏:配置标题、监听左侧点击并弹提示;再通过审查元素修改返回文字、箭头颜色和按下白块。
- 开启组件按需注入并扫描代码质量;把超过 200K 的本地图片替换为网络资源,确认报告变化。
原视频对照
| # | 视频 | 时长 |
|---|---|---|
| 1 | 启用组件按需注入 | 05:01 |
| 2 | Vant组件库-如何查文档 | 14:42 |
| 3 | Vant组件-修改组件样式 | 05:53 |
| 4 | CSS变量-定义全局主题色 | 04:32 |
| 5 | 框架接口-获取App实例 | 05:42 |
| 6 | 框架接口-获取页面栈和页面实例 | 08:40 |
| 7 | 页面生命周期-图书本地存储 | 04:35 |
小结
getApp()与getCurrentPages()是框架级入口,分别面向应用实例和页面实例。- 全局数据可读写,但不会自动驱动页面视图更新,视图更新仍要回到
setData。 - 页面栈最后一项才是当前页面;tabBar 切页后不能按普通历史栈理解。
- 组件库优先查文档,按 props、事件、插槽、外部样式类的顺序找方案。
- 改第三方组件样式要靠审查元素、权重、伪元素和按下态类名,不要盲写类名。
lazyCodeLoading固定在app.json配置;大图优化应走网络资源,不要只依赖分包。onLoad与onShow按业务触发频率选择,首次加载与每次显示要区分。- CSS 变量适合统一维护主题色,集中定义比散落硬编码更易切换。