1. 从零开始:认识微信小程序的“积木块”
刚接触微信小程序开发,你是不是感觉有点懵?看着官方文档里一堆组件,不知道从哪儿下手。别担心,我刚开始也这样。其实,你可以把小程序开发想象成搭乐高。那些基础组件,比如 view、text、image,就是一块块最基础、最核心的积木。官方给了你这些标准件,你的任务就是用它们拼出各种酷炫的页面和功能。这篇文章,我就来当你的“搭积木”教练,抛开那些复杂的理论,直接上手,用最实在的案例,带你把这些基础组件玩转。咱们的目标很简单:看完就能用,用了就有效。
为什么基础组件这么重要?因为它们构成了小程序页面的骨架和血肉。几乎你看到的每一个界面元素,背后都是这些组件在支撑。很多新手一上来就想搞复杂的功能,结果在最简单的布局上卡了半天,其实就是没吃透这些“积木”的特性。比如,你想做个简单的图文列表,用错了容器,可能就会导致样式错乱、点击失效。所以,磨刀不误砍柴工,咱们先把这些基础工具摸熟。
你可能听过 WXML 和 HTML 很像,但千万别把它们完全等同。小程序有自己的一套规则和组件体系。view 确实有点像 div,text 有点像 span,但它们的属性和行为有独特的“小程序风格”。理解这些差异,是你避开很多坑的关键。接下来,我会结合我实际开发中踩过的坑和总结的技巧,带你逐个拆解这些核心组件,保证你学得会、记得住、用得上。
2. 布局基石:view 视图容器的深度玩法
2.1 不只是 div:view 的独门秘籍
一说 view,老手都会告诉你:“它就是小程序的 div。” 这话对,但不全对。view 确实是块级容器,用来划分区域、实现布局,这是它和 div 相似的地方。但小程序为 view 注入了一些非常实用的交互能力,这正是它比普通 div 更香的地方。
最常用的就是 hover-class 属性。这玩意儿干嘛的?简单说,就是给你的视图块添加一个“按压态”效果。用户手指按上去的时候,可以变个色、加个阴影,给个明确的反馈。我做过一个按钮列表,一开始没加这个,上线后用户反馈说“不知道点没点到”,体验很糟糕。加上之后,立马感觉专业多了。用法超简单:
<view class="normal-view" hover-class="pressed-view">
点我试试
</view>
然后在对应的 WXSS 文件里定义 .pressed-view 的样式,比如 background-color: #f0f0f0;。这里有个小坑我踩过:hover-class 指定的样式类,会覆盖原 class 的样式,而不是叠加。所以,你最好在 pressed-view 里写全按压时需要的所有样式,或者用更具体的选择器来管理。
另外三个属性是 hover-start-time、hover-stay-time 和 hover-stop-propagation。hover-start-time 是设置按住多久后才触发按压态,单位是毫秒。有时候为了防止误触,我会设成 50ms,这样轻轻碰一下不会有效果。hover-stay-time 是手指松开后,按压态保持多久,默认是400ms。如果你想要更迅捷的反馈,可以设短一点,比如200ms。hover-stop-propagation 这个属性挺有意思,它用来阻止事件冒泡。啥意思呢?如果你在一个大 view 里嵌套了小 view,两个都设置了 hover-class,点小 view 的时候,大 view 的按压态也会被触发。如果你不想要这个效果,在小 view 上设置 hover-stop-propagation=”true” 就行了。
2.2 实战:用 view 构建卡片式布局
光说不练假把式,咱们来用 view 搭一个现在特别流行的卡片式布局。这种布局干净清爽,信息层级分明,非常适合商品展示、内容列表等场景。
假设我们要做一个简单的文章摘要卡片,包含头像、作者名、文章标题和简短摘要。结构可以这样设计:
<!-- 最外层卡片容器,用于统一阴影、圆角和边距 -->
<view class="article-card">
<!-- 卡片头部,放置作者信息 -->
<view class="card-header">
<image class="author-avatar" src="/images/avatar.png" mode="aspectFill"></image>
<view class="author-info">
<text class="author-name">技术老张</text>
<text class="post-time">2小时前</text>
</view>
</view>
<!-- 卡片主体,放置文章主要内容 -->
<view class="card-body">
<text class="article-title">小程序基础组件避坑指南</text>
<text class="article-summary">本文总结了view、text、image等核心组件在实际开发中容易遇到的5个典型问题及解决方案...</text>
</view>
<!-- 卡片底部,放置操作按钮或标签 -->
<view class="card-footer">
<view class="tag">#前端</view>
<view class="tag">#实战</view>
<view class="read-count">阅读 1.2k</view>
</view>
</view>
对应的样式(WXSS)可以这样写:
.article-card {
margin: 20rpx;
padding: 30rpx;
background-color: #fff;
border-radius: 16rpx;
box-shadow: 0 4rpx 20rpx rgba(0, 0, 0, 0.08);
/* 加上按压反馈 */
hover-class: card-active;
}
.card-active {
background-color: #fafafa;
box-shadow: 0 6rpx 24rpx rgba(0, 0, 0, 0.12);
}
.card-header {
display: flex;
align-items: center;
margin-bottom: 24rpx;
}
.author-avatar {
width: 80rpx;
height: 80rpx;
border-radius: 50%;
margin-right: 20rpx;
}
.author-info {
display: flex;
flex-direction: column;
}
.author-name {
font-size: 32rpx;
font-weight: bold;
color: #333;
}
.post-time {
font-size: 24rpx;
color: #999;
margin-top: 8rpx;
}
.card-body {
margin-bottom: 24rpx;
}
.article-title {
display: block;
font-size: 36rpx;
font-weight: bold;
color: #1a1a1a;
margin-bottom: 16rpx;
line-height: 1.4;
}
.article-summary {
display: block;
font-size: 28rpx;
color: #666;
line-height: 1.5;
}
.card-footer {
display: flex;
align-items: center;
padding-top: 20rpx;
border-top: 1rpx solid #f0f0f0;
}
.tag {
font-size: 22rpx;
color: #007aff;
background-color: #e6f2ff;
padding: 6rpx 16rpx;
border-radius: 20rpx;
margin-right: 16rpx;
}
.read-count {
font-size: 24rpx;
color: #999;
margin-left: auto; /* 利用flex布局的margin-left:auto实现右对齐 */
}
通过这个例子,你可以看到 view 是如何通过嵌套和样式组合,构建出复杂而精美的界面的。关键在于理解盒模型和 Flex 布局,这是用好 view 的基础。多练几次,你就能随心所欲地搭建各种页面结构了。
3. 文字与图片:text 和 image 的细节掌控
3.1 text:让文字听话的秘密
很多新手觉得 text 组件就是显示文字,没什么好讲的。那我问你:小程序里,用户能长按复制一段文字吗?一段文字里的连续空格能正常显示吗?如果你答不上来,那这部分就得好好看了。
text 组件有一个非常重要的属性:user-select。它的默认值是 false,也就是说,默认情况下,小程序里的文字是不能被用户选中复制的。如果你正在做一个显示代码片段、快递单号、订单号的页面,这个功能就至关重要。只需要设置 user-select=”true”,用户就可以长按文字进行复制了。实测下来,这个体验对用户非常友好。
另一个属性 space,是处理空格的。在 HTML 里,连续多个空格默认会被合并成一个,但在小程序里,你可以通过 space 属性控制。space=”ensp” 表示用中文字符空格的一半大小来显示空格,space=”emsp” 用中文字符空格大小,space=”nbsp” 则用根据字体设置的空格大小。这在排版诗歌、对齐文本时特别有用。
decode 属性可以解码 HTML 实体字符。比如你在数据里存了 &(代表 & 符号),如果 decode=”true”,它就会正确显示为 &。这个在处理从后端接口获取的、可能包含特殊字符的文本时很实用。
我举个例子,一个显示用户协议和联系邮箱的场景:
<view class="legal-text">
<text user-select=”true”>用户协议编号:XCX20240527001\n</text>
<text space=”emsp”>请仔细阅读 以上条款(注意这里的空格)\n</text>
<text decode=”true”>联系我们:support&example.com</text>
</view>
这样,协议编号可以复制,空格清晰,邮箱地址也能正确显示。
3.2 image:图片加载优化与交互增强
图片是小程序里最吃性能的资源之一,处理不好,页面卡顿、加载慢的问题就全来了。image 组件的 mode 属性,就是你优化图片显示的第一道利器。
mode 有13种缩放模式,我最常用的是这几种:
- scaleToFill:默认。不保持纵横比缩放图片,使图片的宽高完全拉伸至填满 image 元素。容易变形,慎用。
- aspectFit:保持纵横比缩放图片,使图片的长边能完全显示出来。也就是说,图片会被完整显示,但可能会有留白。适合展示完整的、不可裁剪的图片,如证件照、产品全景图。
- aspectFill:保持纵横比缩放图片,只保证图片的短边能完全显示出来。也就是说,图片会被裁剪,但能填满容器。非常适合做头像、背景图,保证显示区域被填满,且不变形。
- widthFix:宽度不变,高度自动变化,保持原图宽高比不变。这是响应式布局的神器! 我几乎在做任何需要自适应宽度图片的地方都用它。你只需要设定一个宽度(比如100%),高度就会自动按比例计算,完美适配不同屏幕。
<!-- 商品列表中的缩略图,宽度固定,高度自适应 -->
<image src=”{{product.imageUrl}}” mode=”widthFix” style=”width: 100%;” />
<!-- 用户圆形头像,用aspectFill保证填满圆形区域 -->
<image src=”{{user.avatar}}” mode=”aspectFill” style=”width: 120rpx; height: 120rpx; border-radius: 50%;” />
另一个强大的属性是 show-menu-by-longpress。设置为 true 后,用户长按图片,可以调出微信的原生菜单,进行“发送给朋友”、“收藏”、“保存图片”、“搜一搜”等操作。如果你的图片里有二维码,还会出现“识别二维码”的选项。这个功能极大地增强了图片的交互性,几乎零成本提升了用户体验。但要注意,它需要用户长按,属于被动触发,不要把它作为核心功能路径。
图片加载的优化还有懒加载 lazy-load。在列表页中,对于屏幕外的图片,设置 lazy-load=”true” 可以延迟加载,显著提升页面初次渲染速度。不过要记得,它只在页面滚动时生效,对于 scroll-view 内的图片无效。
4. 实现跳转与滚动:navigator 和 scroll-view 的核心应用
4.1 navigator:小程序内的导航专家
页面跳转是小程序最基本的功能,navigator 组件就是实现这个功能的官方推荐方式。它比用 JS 的 wx.navigateTo API 更语义化,也更方便在 WXML 中直接使用。
navigator 的灵魂在于 open-type 属性,它决定了跳转的行为模式。新手最容易搞混的就是这几种:
- navigate (默认):最常用的跳转。可以跳到新页面,新页面左上角有返回按钮,能回到原页面。但不能跳转到 tabBar 页面。相当于在页面栈里压入一个新页面。
- redirect:关闭当前页面,跳转到新页面。当前页面会从页面栈里移除,所以你不能返回。同样不能跳转到 tabBar 页面。适合用在登录后跳转首页这种“一去不复返”的场景。
- switchTab:专门用于跳转到 tabBar 页面,并且会关闭所有非 tabBar 页面。注意:这种模式下不能使用
url传参(query 参数)。如果需要带参数,通常需要借助全局数据或者本地存储。 - reLaunch:关闭所有页面,打开到应用内的某个页面。相当于清空页面栈,然后打开一个新页面。可以跳转到任何页面(包括 tabBar),也可以传参。常用于“回到首页”或“切换用户身份后重置应用状态”这种需要彻底重置导航栈的场景。
我画个简单的流程图帮你理解它们的区别:假设页面栈是 [A, B, C],当前在 C 页面。
navigate到 D:栈变成 [A, B, C, D]。可以从 D 返回到 C。redirect到 D:栈变成 [A, B, D]。无法返回 C。switchTab到 Tab 页 E:栈变成 [E]。A, B, C 全没了。reLaunch到 F:栈变成 [F]。A, B, C 全没了。
传参是通过 url 属性后的 query 字符串实现的:
<navigator url=”/pages/detail/detail?id={{item.id}}&type=news” open-type=”navigate”>
查看详情
</navigator>
在 detail 页面的 onLoad 生命周期函数里,可以通过 options.id 和 options.type 来获取这些参数。
4.2 scroll-view:打造可滚动的定制视图
小程序页面本身有垂直方向的滚动,但如果你想在页面内部做一个横向滚动的标签栏,或者一个固定高度的可滚动列表区域,就需要 scroll-view 了。
scroll-view 必须指定一个固定高度或宽度,才能触发滚动。这是第一个坑。比如做纵向滚动:
<scroll-view scroll-y style=”height: 500rpx;”>
<!-- 很长很长的内容 -->
</scroll-view>
做横向滚动:
<scroll-view scroll-x style=”white-space: nowrap; width: 100%;”>
<view style=”display: inline-block; width: 200rpx;”>标签1</view>
<view style=”display: inline-block; width: 200rpx;”>标签2</view>
<!-- 更多横向排列的项 -->
</scroll-view>
scroll-top 和 scroll-left 属性允许你动态设置滚动条的位置。这在一些场景下非常有用,比如用户点击一个“回到顶部”按钮,或者阅读文章时记录滚动位置,下次进来直接定位。你可以通过数据绑定,将这两个属性与 Page 的 data 关联,然后在 JS 中修改 data 的值来控制滚动位置。
<scroll-view scroll-y style=”height: 80vh;” scroll-top=”{{scrollTop}}” bindscroll=”onScroll”>
<!-- 内容 -->
</scroll-view>
<button bindtap=”scrollToTop”>回到顶部</button>
Page({
data: {
scrollTop: 0
},
onScroll(e) {
// 可以在这里记录滚动位置
console.log(e.detail.scrollTop);
},
scrollToTop() {
this.setData({
scrollTop: 0
})
}
})
注意:
scroll-view内使用text组件时,text必须嵌套在view内,否则会出现滚动异常。另外,在scroll-view里使用textarea、map、canvas、video等原生组件时,会有诸多限制,需要特别注意官方文档的说明。
5. 轮播与表单:swiper 和 button 的进阶技巧
5.1 swiper:不仅仅是轮播图
swiper 组件通常被用来做首页的轮播图,但它其实是一个强大的滑动容器。除了放图片,你完全可以放任何自定义组件,实现如卡片切换、教程引导页、tab内容滑动切换等效果。
它的几个核心属性决定了体验:
indicator-dots:是否显示面板指示点(那些小圆点)。indicator-color/indicator-active-color:分别设置指示点的默认颜色和当前激活的颜色。好好搭配,能提升不少视觉质感。autoplay:是否自动切换。做Banner图一般要开启。interval:自动切换时间间隔,配合autoplay使用。circular:是否采用衔接滑动。强烈建议开启! 开启后,滑动到最后一张时,继续滑动会看到第一张,体验是循环的,非常顺滑。关闭的话,到最后一张就滑不动了,很生硬。vertical:滑动方向是否为纵向。可以做纵向的轮播。
一个常见的轮播图代码示例:
<swiper indicator-dots indicator-color=”rgba(255,255,255,.6)” indicator-active-color=”#fff” autoplay interval=”3000” circular>
<swiper-item wx:for=”{{bannerList}}” wx:key=”id”>
<image src=”{{item.imageUrl}}” mode=”aspectFill” style=”width:100%;height:100%;” bindtap=”goToLink” data-link=”{{item.link}}”/>
</swiper-item>
</swiper>
这里用了 wx:for 循环渲染 swiper-item,每个 item 里放一个 image。图片的 mode 用了 aspectFill 来保证填充不变形,并绑定了点击事件 bindtap,通过 data-* 属性传递点击项对应的链接参数。
swiper 还提供了 bindchange 事件,在 current 改变时会触发。你可以用它来同步显示当前是第几页,或者做一些复杂的联动效果。
5.2 button:样式与开放能力的结合
小程序的 button 组件远不止一个可点击的方块。它深度集成了微信的许多开放能力,是连接用户与微信服务的桥梁。
首先看基础样式控制:
type:决定按钮的主色调。primary是绿色,default是白色,warn是红色。我一般用primary表示主操作(如确定、提交),default表示次要操作(如取消),warn表示警示性操作(如删除)。size:default是默认大小,mini是小尺寸。小尺寸适合放在狭小空间或作为辅助操作。plain:镂空按钮。背景色透明,只有边框和文字有颜色,视觉上更轻量。loading:显示加载中状态。在发起网络请求等待响应时,将其设为true,可以防止用户重复点击,并给予等待反馈。
然后是重头戏 open-type,它赋予了按钮调用微信原生能力的力量:
- contact:打开客服会话。如果用户是在小程序客服会话内点击此按钮,可以回复客服消息。这是实现用户沟通最便捷的方式。
- getPhoneNumber:获取用户手机号。点击后,用户需要确认授权。授权后,可以通过
bindgetphonenumber事件回调获取到加密数据,需在后台配合小程序 API 进行解密才能拿到真实手机号。这是敏感信息,必须谨慎处理。 - getUserInfo(已调整):获取用户信息。现在更推荐使用
wx.getUserProfileAPI。 - launchApp:打开APP。需要APP和小程序关联,且用户已安装该APP。
- openSetting:打开授权设置页。当用户拒绝了某些权限(如位置、相册)后,可以引导用户点击此按钮去重新开启。
- feedback:打开“意见反馈”页面,用户可以直接向开发者反馈问题。
一个调用客服的按钮示例:
<button open-type=”contact” session-from=”weapp”>联系客服</button>
一个获取用户手机号的按钮(需要企业主体小程序):
<button open-type=”getPhoneNumber” bindgetphonenumber=”getPhoneNumberHandler”>获取手机号</button>
Page({
getPhoneNumberHandler(e) {
if (e.detail.errMsg === ‘getPhoneNumber:ok’) {
// e.detail 中包含加密数据 encryptedData 和初始向量 iv
// 需要将 encryptedData 和 iv 发送到自己的服务器,用 session_key 和小程序的 appId 进行解密
console.log(‘加密数据:’, e.detail.encryptedData);
wx.request({
url: ‘你的服务器接口地址’,
method: ‘POST’,
data: {
encryptedData: e.detail.encryptedData,
iv: e.detail.iv
},
success(res) {
console.log(‘解密后的手机号:’, res.data.phoneNumber);
}
})
} else {
// 用户拒绝授权或其他错误
console.log(‘用户拒绝或失败:’, e.detail.errMsg);
}
}
})
用好 button 的开放能力,能极大丰富你小程序的功能,但务必遵守微信的规范,尊重用户隐私。
6. 数据驱动视图:条件渲染与列表渲染实战
小程序是数据驱动的,WXML 里的动态内容全靠数据绑定、条件渲染和列表渲染来实现。这是让页面“活”起来的关键。
6.1 条件渲染:wx:if 与 hidden 的抉择
条件渲染就是根据条件来决定是否渲染某块视图。主要用 wx:if、wx:elif、wx:else 这一套指令。
<view wx:if=”{{score >= 90}}”>优秀</view>
<view wx:elif=”{{score >= 60}}”>及格</view>
<view wx:else>不及格</view>
这里有个重要的性能考量点:wx:if 和 hidden 的区别。
wx:if是真正的条件渲染。当条件为false时,组件不会被渲染到页面结构里,就像它不存在一样。切换条件时,组件有销毁和重新创建的过程。适合在条件切换不频繁,或者初始渲染开销大的情况下使用。hidden只是简单的显示/隐藏控制。无论条件如何,组件始终会被渲染,只是通过样式display: none来控制是否显示。没有组件的销毁与创建过程。适合需要频繁切换显示状态的场景。
所以,如果你有一个很复杂的组件,比如包含大量子节点或复杂逻辑,但只是偶尔显示一下,用 wx:if 更好,因为它不显示时就完全不占资源。如果是一个需要频繁显示/隐藏的简单组件(比如加载中提示),用 hidden 性能更优,因为避免了反复创建销毁的开销。
6.2 列表渲染:wx:for 与 key 的重要性
列表渲染用 wx:for 指令,它可以遍历数组或对象,重复渲染其子组件。wx:for-item 可以指定当前元素的变量名,wx:for-index 指定当前下标的变量名。
<view wx:for=”{{fruitList}}” wx:for-item=”fruit” wx:for-index=”idx”>
{{idx + 1}}. {{fruit.name}} - 价格:{{fruit.price}}元
</view>
wx:key 是列表渲染的性能关键! 它用来指定列表中项目的唯一标识符。当数据改变触发渲染层重新渲染时,框架会带有 key 的组件进行重新排序,而不是销毁和重新创建,从而提高渲染效率。key 的值可以有两种形式:
- 字符串,代表在 for 循环的 array 中 item 的某个 property,该 property 的值需要是列表中唯一的字符串或数字,且不能动态改变。比如
wx:key=”id”。 - 保留关键字
*this,代表 item 本身。这种表示需要 item 本身是一个唯一的字符串或者数字。
我强烈建议,只要你的列表数据有唯一标识字段(如 id),就一定要加上 wx:key。没有 key 的列表,在数据顺序改变时,可能会出现渲染错乱,性能也较差。
一个结合了条件渲染和列表渲染的实战案例:一个任务列表,显示任务项,并根据任务状态显示不同的操作按钮。
<!-- pages/todo/todo.wxml -->
<view class=”task-list”>
<view wx:for=”{{tasks}}” wx:key=”id” class=”task-item {{item.completed ? ‘completed’ : ‘’}}”>
<checkbox checked=”{{item.completed}}” bindtap=”toggleTask” data-id=”{{item.id}}” />
<text class=”task-name”>{{item.name}}</text>
<view wx:if=”{{!item.completed}}” class=”action-btns”>
<button size=”mini” bindtap=”editTask” data-id=”{{item.id}}”>编辑</button>
<button size=”mini” type=”warn” plain bindtap=”deleteTask” data-id=”{{item.id}}”>删除</button>
</view>
<text wx:else class=”completed-text”>已完成</text>
</view>
</view>
// pages/todo/todo.js
Page({
data: {
tasks: [
{ id: 1, name: ‘学习小程序组件’, completed: false },
{ id: 2, name: ‘编写项目文档’, completed: true },
{ id: 3, name: ‘调试接口问题’, completed: false }
]
},
toggleTask(e) {
const id = e.currentTarget.dataset.id;
let tasks = this.data.tasks;
const index = tasks.findIndex(task => task.id === id);
tasks[index].completed = !tasks[index].completed;
this.setData({ tasks });
},
editTask(e) {
const id = e.currentTarget.dataset.id;
// 跳转到编辑页面,传递id
wx.navigateTo({
url: `/pages/edit/edit?id=${id}`
});
},
deleteTask(e) {
const id = e.currentTarget.dataset.id;
wx.showModal({
title: ‘确认删除’,
content: ‘删除后无法恢复’,
success: (res) => {
if (res.confirm) {
let tasks = this.data.tasks.filter(task => task.id !== id);
this.setData({ tasks });
}
}
});
}
})
这个例子综合运用了数据绑定、列表渲染、条件渲染和事件处理。wx:if 根据任务是否完成来控制显示操作按钮还是“已完成”文本。wx:key=”id” 确保了列表更新的高效。事件处理函数通过 data-* 属性获取具体是哪个任务项被操作。通过这样的组合,一个动态交互的任务列表就实现了。多写几次这样的综合案例,你对数据驱动视图的理解会深刻很多。
803



被折叠的 条评论
为什么被折叠?



