小程序 WXML、WXS 和事件传参机制是怎样的?
简化版
WXML 是小程序的模板语言,负责声明页面结构、数据绑定、列表和条件渲染;WXS 是可在视图层使用的脚本能力,适合做轻量格式化;事件通过 bind 或 catch 绑定,参数通常放在 data-* 上,再从 event.currentTarget.dataset 中读取。
详细版
WXML 类似 HTML,但不是 HTML。它提供 {{ }} 数据绑定、wx:if、wx:for、模板、事件绑定和基础组件。
WXS 类似脚本模块,但不是完整 JavaScript 运行环境,常用于视图层轻量计算,例如金额格式化、状态文案映射。复杂业务逻辑仍应放在 JS 逻辑层。
事件绑定常见形式:
<view data-id="{{item.id}}" bindtap="onTapItem">点我</view>
Page({
onTapItem(e) {
const id = e.currentTarget.dataset.id
}
})
bind 不阻止冒泡,catch 会阻止冒泡;target 是触发事件的源节点,currentTarget 是当前绑定事件的节点。
完整版教学
一、WXML 是声明式结构,不是浏览器 DOM
小程序没有让开发者直接操作浏览器 DOM。WXML 负责描述页面结构,逻辑层通过 setData 把数据传给渲染层,渲染层再根据模板更新视图。
<view class="goods">
<text>{{title}}</text>
<button bindtap="buy">购买</button>
</view>
这段代码不是浏览器里的真实 HTML,而是小程序框架识别的模板。它最后会映射到宿主客户端的渲染能力上,因此很多 Web API、DOM API、BOM API 在小程序里并不存在。
面试回答时要把“小程序像前端”与“小程序不是网页”区分开:写法类似组件化前端,但运行环境、渲染管线和能力边界都由微信客户端控制。
二、数据绑定、条件渲染和列表渲染
WXML 使用 {{ }} 做数据绑定,常见指令有 wx:if、hidden、wx:for。wx:if 会控制节点是否渲染,hidden 更像显示隐藏,节点仍保留。
<view wx:if="{{isLogin}}">已登录</view>
<view wx:else>未登录</view>
<view wx:for="{{list}}" wx:key="id">
{{index}} - {{item.name}}
</view>
如果一个列表有 100 条数据,缺少稳定 wx:key 时,框架在列表变更时更难复用节点,可能导致渲染成本上升或状态错位。wx:key="id" 的意义和前端框架中的 key 类似,都是帮助框架识别“谁是谁”。
| 写法 | 节点是否创建 | 适合场景 |
|---|---|---|
wx:if | 条件为真才创建 | 切换不频繁、初始成本要低 |
hidden | 创建后隐藏 | 切换频繁、保留状态 |
wx:for | 按数组渲染 | 列表数据展示 |
三、WXS 适合轻量视图计算
WXS 可以写在 .wxs 文件或 WXML 的 <wxs> 标签中,常用于视图层格式化。它有自己的模块作用域和语法限制,不能当成完整 JS 环境使用。
<wxs module="fmt">
module.exports.price = function (n) {
return '¥' + (n / 100).toFixed(2)
}
</wxs>
<text>{{fmt.price(priceCent)}}</text>
假设接口返回 priceCent=1299,WXS 可以直接在模板里渲染为 ¥12.99,避免每次在 JS 层额外维护一个展示字段。它的价值是靠近视图层、减少简单格式化的样板代码。
但复杂逻辑不适合放 WXS。比如订单优惠计算、权限判断、库存校验都应该在逻辑层或服务端完成,不能为了“模板里方便”把业务规则散落到 WXS。
四、事件绑定中的 bind 和 catch
小程序事件绑定常见写法是 bindtap、catchtap,也可以写成 bind:tap、catch:tap。区别在于 bind 不阻止冒泡,catch 会阻止事件继续向上冒泡。
<view bindtap="onCardTap">
<button catchtap="onButtonTap">按钮</button>
</view>
点击按钮时,onButtonTap 会执行,但由于使用 catchtap,外层 onCardTap 不会执行。如果按钮用的是 bindtap,事件会继续冒泡到外层。
点击 button
-> button 监听器
-> 若 bind:继续冒泡到 card
-> 若 catch:停止冒泡
在商品卡片里,外层点击进详情,内层按钮加入购物车,就经常需要 catchtap,否则用户点加入购物车时还会跳详情页。
五、事件传参为什么用 data-*
WXML 事件绑定不能像 Web 框架那样直接写复杂函数调用。常见做法是把参数写到节点的 data-* 属性中,再从事件对象读取。
<view
wx:for="{{goodsList}}"
wx:key="id"
data-id="{{item.id}}"
data-index="{{index}}"
bindtap="onSelect"
>
{{item.name}}
</view>
Page({
onSelect(e) {
const { id, index } = e.currentTarget.dataset
console.log(id, index)
}
})
要注意 data-user-id 会转换成 dataset.userId,而大小写和连字符组合可能带来意外转换。工程中建议统一使用短横线小写形式,例如 data-order-id。
记忆钩子:小程序事件参数不是“函数参数”,而是“节点数据 + 事件对象”。参数放
data-*,读取看currentTarget.dataset。
六、target 和 currentTarget 的区别
event.target 表示真正触发事件的源节点,event.currentTarget 表示当前绑定事件处理函数的节点。嵌套结构里二者经常不同。
<view data-id="card-1" bindtap="onTapCard">
<text data-id="text-1">标题</text>
</view>
点击 text 时,target.dataset.id 可能是 text-1,而 currentTarget.dataset.id 是 card-1。如果你想拿绑定在卡片上的参数,应读 currentTarget。
这也是面试常见陷阱。很多人事件代理写错,就是因为把源节点参数和绑定节点参数混在一起。列表点击、卡片按钮、父子节点嵌套时尤其要谨慎。
七、常见误区与追问
- 误区:WXML 就是 HTML。 WXML 是小程序模板语言,不提供浏览器 DOM 和完整 Web API。
- 误区:WXS 可以替代页面 JS。 WXS 适合轻量视图格式化,复杂业务逻辑应放逻辑层或服务端。
- 误区:bindtap 会阻止冒泡。
bind不阻止冒泡,catch才会阻止冒泡。 - 追问:target 和 currentTarget 怎么选? 要拿触发源节点读
target,要拿绑定事件节点读currentTarget。 - 追问:为什么列表要写 wx:key? 稳定 key 能帮助框架复用节点,减少列表更新成本和状态错位。
- 追问:事件怎么传多个参数? 用多个
data-*属性,事件中从currentTarget.dataset读取。
八、加强记忆
WXML 负责结构,WXS 负责轻量展示计算,事件负责把用户操作带回逻辑层。答题时抓三组对比:wx:if 和 hidden、bind 和 catch、target 和 currentTarget。再补上 data-* 传参和 wx:key,小程序模板与事件题基本就完整了。