# 视图层（WXML / WXSS / WXS）

> 官方文档：https://developers.weixin.qq.com/miniprogram/dev/framework/view/

## WXML — 模板语法

### 数据绑定

```xml
<!-- 简单绑定 -->
<view>{{ message }}</view>

<!-- 组件属性绑定（需在双引号内） -->
<view id="item-{{id}}"></view>

<!-- 控制属性绑定 -->
<view wx:if="{{condition}}"></view>

<!-- 关键字绑定（注意 true/false 需要在 {{}} 内） -->
<checkbox checked="{{false}}"></checkbox>
<!-- ⚠️ 错误写法：checked="false" 会被当作字符串 "false"，结果为 true -->

<!-- 运算 -->
<view>{{ a + b }} + {{ c }} + d</view>
<view>{{"hello " + name}}</view>
<view>{{object.key}} {{array[0]}}</view>

<!-- 三元运算 -->
<view hidden="{{flag ? true : false}}">Hidden</view>

<!-- 组合（数组） -->
<view wx:for="{{[zero, 1, 2, 3, 4]}}">{{item}}</view>

<!-- 组合（对象） -->
<template is="objectCombine" data="{{for: a, bar: b}}"></template>
<!-- 展开运算符 -->
<template is="objectCombine" data="{{...obj1, ...obj2, e: 5}}"></template>
```

### 列表渲染 wx:for

```xml
<!-- 基本用法：默认 item 和 index -->
<view wx:for="{{array}}">
  {{index}}: {{item.message}}
</view>

<!-- 自定义变量名 -->
<view wx:for="{{array}}" wx:for-index="idx" wx:for-item="itemName">
  {{idx}}: {{itemName.message}}
</view>

<!-- 嵌套 -->
<view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="i">
  <view wx:for="{{[1, 2, 3, 4, 5, 6, 7, 8, 9]}}" wx:for-item="j">
    <view wx:if="{{i <= j}}">
      {{i}} * {{j}} = {{i * j}}
    </view>
  </view>
</view>

<!-- ⚠️ wx:key 非常重要，用于列表项的唯一标识 -->
<!-- 值为字符串：item 的某个 property 名（该 property 值需唯一） -->
<switch wx:for="{{objectArray}}" wx:key="unique">{{item.id}}</switch>

<!-- 值为 *this：item 本身是唯一字符串或数字 -->
<switch wx:for="{{numberArray}}" wx:key="*this">{{item}}</switch>
```

**wx:key 的作用**：当数据改变触发重新渲染时，带有 key 的组件会被重新排序而非重新创建，保持自身状态（如 `<input>` 的输入内容、`<switch>` 的选中状态）。

### 条件渲染

```xml
<!-- wx:if / wx:elif / wx:else -->
<view wx:if="{{length > 5}}">1</view>
<view wx:elif="{{length > 2}}">2</view>
<view wx:else>3</view>

<!-- block wx:if（不会渲染为真实 DOM） -->
<block wx:if="{{true}}">
  <view>view1</view>
  <view>view2</view>
</block>
```

**wx:if vs hidden**：
- `wx:if` 是惰性的，条件为 false 时不渲染，切换时销毁/重建
- `hidden` 始终渲染，只是切换显示/隐藏（类似 CSS display:none）
- 频繁切换用 `hidden`，运行时条件不大可能改变用 `wx:if`

### 模板 template

```xml
<!-- 定义模板 -->
<template name="msgItem">
  <view>
    <text>{{index}}: {{msg}}</text>
    <text>Time: {{time}}</text>
  </view>
</template>

<!-- 使用模板 -->
<template is="msgItem" data="{{...item}}"/>

<!-- 动态模板名 -->
<template is="{{item % 2 == 0 ? 'even' : 'odd'}}"/>
```

### 引用

```xml
<!-- import：引入目标文件中定义的 template -->
<import src="item.wxml"/>
<template is="item" data="{{text: 'forbar'}}"/>
<!-- ⚠️ import 有作用域：只会引入目标文件中定义的 template，不会引入目标文件 import 的 template（不递归） -->

<!-- include：将目标文件除 <template/> <wxs/> 外的整个代码引入 -->
<include src="header.wxml"/>
<view>body</view>
<include src="footer.wxml"/>
```

## WXSS — 样式

### rpx 单位

rpx（responsive pixel）：可以根据屏幕宽度进行自适应。规定屏幕宽为 750rpx。

| 设备 | rpx 换算 px | px 换算 rpx |
|------|------------|------------|
| iPhone5 | 1rpx = 0.42px | 1px = 2.34rpx |
| iPhone6 | 1rpx = 0.5px | 1px = 2rpx |
| iPhone6 Plus | 1rpx = 0.552px | 1px = 1.81rpx |

**建议**：开发时以 iPhone6 为视觉稿标准（750px 宽），1px = 1rpx。

### 样式导入

```css
/* common.wxss */
.small-p { padding: 5px; }

/* app.wxss */
@import "common.wxss";
.middle-p { padding: 15px; }
```

### 选择器支持

| 选择器 | 示例 | 说明 |
|--------|------|------|
| .class | `.intro` | 类选择器 |
| #id | `#firstname` | ID 选择器 |
| element | `view` | 元素选择器 |
| element, element | `view, checkbox` | 群组选择器 |
| ::after | `view::after` | 伪元素 |
| ::before | `view::before` | 伪元素 |

### 内联样式

```xml
<!-- style：动态样式，运行时解析，尽量避免静态样式写在 style 中 -->
<view style="color:{{color}};"/>

<!-- class：静态样式写在 class 中 -->
<view class="normal_view"/>
```

### 全局样式与局部样式
- `app.wxss` 为全局样式，作用于每个页面
- 页面的 `.wxss` 只对当前页面生效，会覆盖 `app.wxss` 中相同的选择器

## WXS（WeiXin Script）

WXS 是小程序的一套脚本语言，可以在 WXML 中使用。**WXS 运行在视图层**，比 JS 逻辑层快（不需要跨线程通信）。

```xml
<!-- 内联 WXS -->
<wxs module="m1">
var msg = "hello world";
module.exports.message = msg;
</wxs>
<view>{{m1.message}}</view>

<!-- 外部 WXS 文件 -->
<wxs src="./tools.wxs" module="tools"/>
<view>{{tools.msg}}</view>
<view>{{tools.bar(tools.FOO)}}</view>
```

```javascript
// tools.wxs
var foo = "'hello world' from tools.wxs"
var bar = function(d) {
  return d
}
module.exports = {
  FOO: foo,
  bar: bar,
}
// ⚠️ WXS 不支持 ES6 语法（箭头函数、let/const、解构等）
// ⚠️ WXS 中不能调用小程序 API（wx.xxx）
```

**WXS 典型用途**：
- 格式化数据（日期、金额、文本截断）
- 在视图层做简单计算，避免 setData 开销
- 响应事件（WXS 事件响应，iOS 上性能更好）

## 事件系统

### 事件分类

| 类型 | 说明 | 示例 |
|------|------|------|
| 冒泡事件 | 向父节点传递 | tap, longpress, touchstart, touchmove, touchend, touchcancel |
| 非冒泡事件 | 不向父节点传递 | submit, input, scroll 等组件特有事件 |

### 事件绑定

```xml
<!-- bind：不阻止冒泡 -->
<view bindtap="handleTap">Click me</view>
<view bind:tap="handleTap">Click me</view>

<!-- catch：阻止冒泡 -->
<view catchtap="handleTap">Click me</view>

<!-- mut-bind：互斥事件绑定（基础库 2.8.2+） -->
<view mut-bind:tap="handleTap">
  <button mut-bind:tap="handleButtonTap">按钮</button>
</view>
<!-- 同一冒泡路径上的 mut-bind 只会有一个被触发 -->

<!-- capture-bind：捕获阶段绑定 -->
<view capture-bind:tap="handleCapture">
  <view bindtap="handleTap">inner</view>
</view>

<!-- capture-catch：捕获阶段中断 -->
<view capture-catch:tap="handleCapture">
  <view bindtap="handleTap">inner（不会触发）</view>
</view>
```

### 事件对象

```javascript
Page({
  handleTap(e) {
    e.type          // 事件类型，如 "tap"
    e.timeStamp     // 事件生成时的时间戳
    e.target        // 触发事件的源组件（可能是子组件）
    e.currentTarget // 事件绑定的当前组件
    e.detail        // 额外信息，如 tap 的 { x, y }
    e.touches       // 触摸事件的触摸点信息数组
    e.changedTouches // 变化的触摸点信息数组
    e.mark          // 事件标记（基础库 2.7.1+）

    // target 和 currentTarget 的区别
    e.target.id         // 触发事件的组件 id
    e.target.dataset    // 触发事件的组件的 data-xxx 属性集合
    e.currentTarget.id  // 绑定事件的组件 id
    e.currentTarget.dataset
  }
})
```

### dataset

```xml
<!-- data-xxx 属性传递数据 -->
<view data-alpha-beta="1" data-alphaBeta="2" bindtap="handleTap">
  Click
</view>
```

```javascript
handleTap(e) {
  e.currentTarget.dataset.alphaBeta // "1"（连字符转驼峰）
  e.currentTarget.dataset.alphabeta // "2"（大写转小写）
}
```

### mark（基础库 2.7.1+）

```xml
<!-- mark 可以在冒泡路径上所有节点收集 -->
<view mark:myMark="last" bindtap="bindViewTap">
  <button mark:anotherMark="leaf" bindtap="bindButtonTap">按钮</button>
</view>
```

```javascript
bindButtonTap(e) {
  e.mark // { myMark: "last", anotherMark: "leaf" }
  // mark 会合并冒泡路径上所有的 mark
}
```

## 在线查询

如需更详细信息，可抓取：
- WXML 语法：https://developers.weixin.qq.com/miniprogram/dev/reference/wxml/
- WXSS：https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxss.html
- WXS：https://developers.weixin.qq.com/miniprogram/dev/reference/wxs/
- 事件：https://developers.weixin.qq.com/miniprogram/dev/framework/view/wxml/event.html
