返回博客

WXML 不是 HTML:微信小程序特殊字符渲染的八个反直觉坑

一个常见的上线前惊吓:把 🎂 写进 WXML,本以为会渲染成生日蛋糕 emoji,结果真机一跑,用户看到的是字面量 🎂——六个字符,毫无装饰感可言。排查半小时后才意识到:WXML 根本不是 HTML,它有自己的编译器和渲染管线,大量浏览器里天经地义的写法在这里静默失效。

踩过这条路之后,我把遇到的八类坑汇成一篇,每个坑都附根因和可直接复用的正解。

现象

  • 🎂 等 HTML 数字实体显示为原始字符串
  • .wxs 文件里的 (▲)渲染成字面量
  • wx:if/elif/else 链外面的兄弟节点("没有更多了"、加载更多组件)有时不渲染
  • 布尔初始值为 true 导致空状态永远不出现
  • flex 比例条在极端比例(如 2% vs 98%)时文字溢出

根因逐一拆解

Gotcha 1:HTML 实体在 WXML 里不解码

坑:WXML 编译器不解析 HTML 数字字符引用

&#NNNNN; 是 HTML 规范的实体编码,浏览器 HTML 解析器负责解码。WXML 有独立的编译器,不走 HTML 解析器,所以 🎂 原样输出为六个字符。

<!-- 错误:显示字面量 &#127874; -->
<view>&#127874;</view>
正解:用 JS 数据绑定注入 Unicode
<!-- WXML -->
<view>{{birthdayEmoji}}</view>
// Page data
data: {
  birthdayEmoji: '🎂'  // U+1F382,ES5 代理对,兼容所有微信版本
}

BMP 字符(U+0000–U+FFFF)单个 \uXXXX 即可;SMP emoji(U+10000+)需要代理对,或用 String.fromCodePoint(0x1F382)(ES6+)。


Gotcha 2:WXS 里 \uXXXX 可能渲染为字面量

坑:WXS 是独立沙箱,Unicode 转义并非总有效

WXS 有自己的解析器,对几何符号(,▲▼■○等)和箭头()的 \u 转义处理不稳定,实测会直接吐出 字面串。

// 错误:.wxs 文件
function arrow(cur, base) {
  if (cur > base) return ' ▲'  // 可能显示 "▲"
  if (cur < base) return ' ▼'
  return ''
}
正解:.wxs 文件直接用 UTF-8 字符
// 正确:.wxs 文件
function arrow(cur, base) {
  if (cur > base) return ' ▲'   // 直接嵌入字符
  if (cur < base) return ' ▼'
  return ''
}
module.exports = { arrow: arrow }

WXS 文件保存为 UTF-8,直接嵌入字符总是可靠的。\u 转义只留给不可见控制字符。


Gotcha 3:wx:if/elif/else 链外的兄弟节点可能不渲染

坑:条件链后的兄弟节点有时被跳过

理论上 wx:if/elif/else 链和其后的兄弟节点是独立关系。但实践中,当链结构复杂或兄弟节点自身也带 wx:if 时,渲染引擎可能跳过它们。"没有更多了"文字和加载更多组件是重灾区。

<view wx:if="{{loading}}">骨架屏</view>
<view wx:elif="{{isEmpty}}">暂无数据</view>
<view wx:else>列表内容...</view>
<!-- 以下两行有时不渲染 -->
<x-more show="{{showMore}}" />
<view wx:if="{{loaded && !showMore}}">- 没有更多了 -</view>
正解:把尾部元素移进对应分支内
<view wx:if="{{loading}}">骨架屏</view>
<view wx:elif="{{isEmpty}}">暂无数据</view>
<view wx:else>
  <block wx:for="{{items}}" wx:key="id">
    <view>{{item.name}}</view>
  </block>
  <!-- 放在 else 分支内,渲染保证 -->
  <x-more show="{{showMore}}" />
  <view wx:if="{{loaded && !showMore}}">- 没有更多了 -</view>
</view>

Gotcha 4:布尔初始值 true 导致空状态永远不出现

坑:初始值为 true,空状态条件永远不成立

如果列表是否有数据用一个布尔 isHaveItems 控制,且初始值设为 true,那么 wx:if="{{!isHaveItems}}" 从启动到 API 返回空数组这段时间内是 false——更致命的是,即便 API 返回空列表后你正确地把它设为 false,在某些初始化竞态下用户也可能永远看不到空状态。

正解:初始值 false + loaded 守卫双保险
data: {
  isHaveItems: false,
  loaded: false
}
<!-- loaded 守卫防止数据到来前的空状态闪烁 -->
<view wx:if="{{!isHaveItems && loaded}}">暂无数据</view>

Gotcha 5:flex 比例极端时文字溢出

比例条用 style="flex: {{count}}" 实现时,若某段占比低于约 8%,文字必然溢出。加 overflow: hidden; white-space: nowrap 只是截断,根本解法是按占比阈值控制文字可见性

showFemaleLabel: total > 0 && (female / total >= 0.08)
<view style="flex: {{female}}; overflow: hidden; white-space: nowrap">
  <text wx:if="{{showFemaleLabel}}">女 {{female}}</text>
</view>

其他快速备忘

| 坑 | 正解 | |---|---| | iOS new Date('2024-01-01') 报错 | 改用 new Date('2024/01/01') | | WXS 里用了 new Date() | 改用 getDate(),WXS 无 new Date | | WXS 里用了箭头函数/模板字符串 | 改回 ES5 语法,WXS 是受限沙箱 |

一句话外卖

WXML 是独立的模板语言,不是 HTML 子集——凡是依赖 HTML 规范(实体解码、标准 JS 运行时、兄弟节点渲染保证)的写法,都要先验证微信编译器是否支持。

实机验证清单

  1. DevTools 模拟器通过后,必须在 iOS + Android 真机各验一遍
  2. 特殊字符:确认显示为字形,而非 &#NNNNN;\uXXXX 字面串
  3. 比例条:用 99:1 极端数据测文字是否溢出
  4. 空状态:模拟 API 返回空数组,确认空态出现且无闪烁