4、小程序基础语法:WXML模板语法、WXSS样式语法、JS逻辑交互、数据绑定与渲染

好,咱们进入正题。小程序的基础语法,说白了就是三件套:WXML(结构)、WXSS(样式)、JS(逻辑)。再加上一个核心概念——数据绑定。这四个东西搞明白了,你就能写出一个能跑、能看、能交互的小程序了。

我记得刚接触小程序时,第一反应是:“这不就是HTML+CSS+JS吗?” 嗯,确实很像,但坑也不少。咱们一个一个说。

4.1 WXML:长得像HTML,但规矩更多

WXML 是微信定义的模板语言。它跟 HTML 最大的区别是:标签是微信自己封装的,比如 <view> 代替 <div><text> 代替 <span>

我个人习惯把 WXML 理解为“带数据能力的 HTML”。它支持条件渲染、循环渲染,还能直接绑定 JS 里的数据。

核心语法示例:

<!-- 数据绑定 -->
<view>{{ message }}</view>

<!-- 条件渲染 -->
<view wx:if="{{ isShow }}">我出现了</view>

<!-- 循环渲染 -->
<view wx:for="{{ list }}" wx:key="id">
  {{ item.name }}
</view>

这里有个坑,我曾经踩过:wx:key 必须写,否则列表更新时性能会崩。你想想看,如果列表有100项,没有 key 的话,小程序会全部重新渲染,那体验就太差了。

4.2 WXSS:CSS的“阉割版”+“增强版”

WXSS 大部分语法跟 CSS 一样,但有几个关键区别:

  • 不支持通配符 *,所以不能写 * { margin: 0; }。我刚开始写的时候习惯性写了个 *,结果样式全没生效,排查了半天。
  • 新增了 rpx 单位,这是微信的响应式单位。简单说:1rpx = 屏幕宽度的 1/750。设计稿如果是 750px 宽,你直接写多少 px 就转成多少 rpx,省心。
  • 支持 @import 导入样式,但注意路径要写相对路径。

我的建议:写样式时尽量用 rpx,别用 px。尤其是字体大小、边距、宽高,用 rpx 能保证在不同屏幕下比例一致。我在项目中遇到过用 px 写的按钮,在 iPhone 12 上正常,在 iPhone SE 上就挤成一团了。

4.3 JS逻辑交互:Page 函数是核心

小程序的 JS 逻辑,主要写在 Page() 函数里。每个页面都有一个对应的 JS 文件,里面定义数据、生命周期、事件处理等。

Page({
  data: {
    count: 0
  },
  // 生命周期
  onLoad() {
    console.log('页面加载了')
  },
  // 事件处理
  handleTap() {
    this.setData({
      count: this.data.count + 1
    })
  }
})

这里有个关键点:修改数据必须用 this.setData(),不能直接 this.data.count = 1。为什么?因为 setData 会通知视图层更新,直接赋值只是改了内存里的值,页面不会变。

注意:setData 是异步的,而且频繁调用会影响性能。我曾经在一个循环里调了 100 次 setData,结果页面卡得动不了。后来改成一次性传一个对象,问题就解决了。

4.4 数据绑定与渲染:{{ }} 是桥梁

数据绑定是小程序最核心的机制。说白了,就是 JS 里的数据通过 {{ }} 传到 WXML 里,然后渲染出来。

举个例子:

// JS
Page({
  data: {
    user: {
      name: '张三',
      age: 25
    },
    isVip: true
  }
})

// WXML
<view>姓名:{{ user.name }}</view>
<view>年龄:{{ user.age }}</view>
<view wx:if="{{ isVip }}">VIP 用户</view>

数据绑定支持表达式,比如 {{ user.age > 18 ? '成年' : '未成年' }}。但注意,不支持复杂的逻辑运算,比如函数调用、循环等。如果你需要复杂处理,建议在 JS 里先算好,再绑定。

避坑指南:我曾经在 {{ }} 里写了个 Math.random(),想生成随机数,结果发现每次渲染结果都一样。因为 WXML 里的表达式只在数据变化时重新计算,不会自动刷新。正确的做法是在 JS 里生成随机数,然后 setData 更新。

4.5 条件渲染 vs 隐藏渲染

小程序提供了两种控制元素显示的方式:

方式 语法 特点
条件渲染 wx:if 条件为 false 时,元素不渲染(DOM 中不存在)
隐藏渲染 hidden 条件为 false 时,元素隐藏(display: none)

怎么选?我的经验是:频繁切换用 hidden,一次性判断用 wx:if。比如一个弹窗,用户点开、关闭、再点开,用 hidden 更合适,因为不用反复创建和销毁 DOM。而像“是否显示用户头像”这种一次性的判断,用 wx:if 更省资源。

4.6 列表渲染:wx:for 的细节

列表渲染是 WXML 里最常用的功能之一。基本语法:

<view wx:for="{{ items }}" wx:for-item="item" wx:for-index="index" wx:key="id">
  {{ index }} - {{ item.name }}
</view>

这里有几个细节:

  • wx:for-item 可以自定义循环变量名,默认是 item
  • wx:for-index 可以自定义索引名,默认是 index
  • wx:key 必须写,而且值最好是唯一标识,比如 id。如果列表是静态的,可以用 *this 表示用元素本身作为 key

小技巧:如果你在循环里嵌套循环,一定要给内层的 wx:for-itemwx:for-index 起不同的名字,否则会冲突。我见过有人内外都用 item,结果内层把外层的 item 覆盖了,数据全乱了。

4.7 事件绑定:bind 和 catch 的区别

小程序里绑定事件用 bindcatch,后面跟事件名,比如 bindtapcatchtap

区别在于:

  • bind:事件会冒泡(从子元素传到父元素)
  • catch:事件不会冒泡(阻止传播)

举个例子:

<view bindtap="parentTap">
  父元素
  <view bindtap="childTap">子元素</view>
</view>

点击子元素时,会先触发 childTap,再触发 parentTap。如果你把子元素的 bindtap 改成 catchtap,那父元素的 parentTap 就不会触发了。

嗯,这里要注意:不要滥用 catch。我见过有人把所有事件都写成 catch,结果页面上的交互逻辑全乱了,因为该冒泡的事件没冒泡。

4.8 总结一下

小程序的基础语法,说白了就是:

  • WXML 负责结构,用 {{ }} 绑定数据
  • WXSS 负责样式,用 rpx 做响应式
  • JS 负责逻辑,用 setData 更新视图
  • 数据绑定 是桥梁,把 JS 和 WXML 连起来

我个人觉得,学小程序语法最好的方式就是动手写。你打开开发者工具,新建一个页面,试着写一个列表、加一个点击事件、改一下样式。遇到问题就查文档,踩过坑就记住了。我当年就是这么过来的,现在回头看,那些坑其实都是财富。

下一章咱们聊聊小程序的生命周期,那可是个重头戏。准备好了吗?