表达式绑定
Alpine.js 支持在 HTML 属性里内联 JavaScript 表达式, 让数据绑定不仅是"直接显示变量", 还能做运算、格式化、条件判断、列表循环。本文档覆盖 Alpine 在 Darra HMI 中的主要用法。
表达式基础
Alpine 的 x-* 指令值就是 JavaScript 表达式, 在 x-data 作用域内求值:
<div x-data="{ n: 0, running: false, list: [1,2,3] }">
<span x-text="n"></span> <!-- 变量 -->
<span x-text="n * 10"></span> <!-- 运算 -->
<span x-text="running ? '运行' : '停止'"></span> <!-- 三元 -->
<span x-text="list.length"></span> <!-- 属性 -->
<span x-text="list.map(x => x*2).join(',')"></span> <!-- 函数链 -->
</div>
数学运算
<div x-data="{ t: 0, r: 0, scale: 1.8 }"
x-init="
darra.bind('DB1.TempC', v => t = v)
darra.bind('DB1.Ratio', v => r = v)">
<!-- 单位换算: C → F -->
华氏温度: <span x-text="(t * scale + 32).toFixed(1)"></span> °F
<!-- 百分比 -->
利用率: <span x-text="(r * 100).toFixed(1)"></span> %
<!-- 分段 -->
状态: <span x-text="t < 20 ? '低' : t < 60 ? '正常' : '高'"></span>
<!-- Math 函数 -->
绝对值: <span x-text="Math.abs(t)"></span>
保留精度: <span x-text="Math.round(t * 100) / 100"></span>
</div>
格式化 / Filter 风格
Alpine 没有内置 filter, 用普通函数代替:
<script>
// ----- 在 JsCode 全局定义 -----
function fmt(v, decimals = 2, unit = '') {
if (v === null || v === undefined || isNaN(v)) return '--'
return Number(v).toFixed(decimals) + (unit ? ' ' + unit : '')
}
function fmtDate(ts) {
return new Date(ts).toLocaleString('zh-CN')
}
function fmtDuration(ms) {
const s = Math.floor(ms / 1000)
const h = Math.floor(s / 3600), m = Math.floor((s % 3600) / 60)
return `${h}小时${m}分`
}
</script>
<div x-data="{ t: 0, startTs: 0 }"
x-init="
darra.bind('DB1.Temp', v => t = v)
darra.bind('DB1.StartTime', v => startTs = v)">
温度: <span x-text="fmt(t, 2, '℃')"></span>
启动时间: <span x-text="fmtDate(startTs)"></span>
运行时长: <span x-text="fmtDuration(Date.now() - startTs)"></span>
</div>
条件渲染
x-show (切 display)
<div x-data="{ level: 0 }"
x-init="darra.bind('DB1.Level', v => level = v)">
<div x-show="level < 20" style="color:red">液位低</div>
<div x-show="level >= 20 && level <= 80" style="color:green">正常</div>
<div x-show="level > 80" style="color:orange">液位高</div>
</div>
x-if (增删 DOM)
<template x-if="level > 95">
<div class="alarm-panel">
<h2>紧急: 液位超限</h2>
<darra-button var="M0.0" mode="momentary">排空</darra-button>
</div>
</template>
差异:
| 指令 | DOM | 性能 | 适合 |
|---|---|---|---|
x-show | 始终存在, display 切换 | 快 | 频繁切换 / 短文本 |
x-if | 条件满足才存在 | 慢 (增删) | 重组件 / 不常切换 |
列表循环 (x-for)
<div x-data="{ axes: [
{ id:0, name:'X', pos:0 },
{ id:1, name:'Y', pos:0 },
{ id:2, name:'Z', pos:0 }
] }"
x-init="
axes.forEach(a => {
darra.bind(`DB50.Axis${a.id}.Pos`, v => a.pos = v)
})">
<template x-for="axis in axes" :key="axis.id">
<div class="axis-row">
<span x-text="axis.name"></span>
轴: <span x-text="axis.pos.toFixed(3)"></span> mm
</div>
</template>
</div>
遍历 index
<template x-for="(item, idx) in items" :key="idx">
<div>
<span x-text="idx + 1"></span>.
<span x-text="item.name"></span>
</div>
</template>
过滤 + 排序
<div x-data="{ alarms: [] }"
x-init="
darra.on('alarm', a => {
alarms.push({ ...a, ts: Date.now() })
if (alarms.length > 100) alarms.shift()
})">
<!-- 只显示错误级别, 最新在前 -->
<template x-for="a in alarms.filter(x => x.level === 'error').slice().reverse()" :key="a.ts">
<div class="alarm-row">
<span x-text="new Date(a.ts).toLocaleTimeString()"></span>
<span x-text="a.source"></span>:
<span x-text="a.message"></span>
</div>
</template>
</div>
多变量聚合 (bindGroup + Alpine)
<div x-data="{ t1: 0, t2: 0, t3: 0 }"
x-init="darra.bindGroup(['DB1.T1','DB1.T2','DB1.T3'], vs => {
t1 = vs['DB1.T1']; t2 = vs['DB1.T2']; t3 = vs['DB1.T3']
})">
平均: <span x-text="((t1 + t2 + t3) / 3).toFixed(2)"></span>
最大: <span x-text="Math.max(t1, t2, t3).toFixed(2)"></span>
最小: <span x-text="Math.min(t1, t2, t3).toFixed(2)"></span>
标准差: <span x-text="
(() => {
const m = (t1 + t2 + t3) / 3
const v = ((t1-m)**2 + (t2-m)**2 + (t3-m)**2) / 3
return Math.sqrt(v).toFixed(3)
})()
"></span>
</div>
复杂逻辑避免堆在
x-text里, 抽成$data.computeXxx()函数。
带参数的 computed
Alpine 没有 Vue 的 computed, 用 getter 实现:
<div x-data="{
temp: 0,
pressure: 0,
get level() {
if (this.temp > 80 || this.pressure > 8) return 'critical'
if (this.temp > 60 || this.pressure > 5) return 'warning'
return 'normal'
},
get colorClass() {
return `badge-${this.level}`
}
}"
x-init="
darra.bind('DB1.Temp', v => temp = v)
darra.bind('DB1.Pressure', v => pressure = v)">
<div :class="colorClass" x-text="level"></div>
</div>
全局函数扩展
在 JsCode 里注册 Alpine magic:
document.addEventListener('alpine:init', () => {
// $darra magic 属性
Alpine.magic('darra', () => ({
get: (v) => darra.get(v),
write: (v, val) => darra.write(v, val)
}))
// $fmt magic 格式化
Alpine.magic('fmt', () => ({
n: (v, d = 2) => Number(v).toFixed(d),
pct: (v) => (v * 100).toFixed(1) + '%',
time: (ts) => new Date(ts).toLocaleTimeString()
}))
})
使用:
<div>
温度: <span x-text="$fmt.n($darra.get('DB1.Temp'), 1)"></span>
<button @click="$darra.write('M0.0', true)">启动</button>
</div>
表达式限制
| 能做 | 不能做 |
|---|---|
| 变量引用 / 运算 / 三元 | var / let / function 声明 |
调用 $data 内函数 | 定义新函数 (需在 x-data 内先声明) |
访问 window.* 全局 | 异步 (不等 await) |
Math.* / Number.* / String.* | 多语句 (只能单表达式) |
| 数组/对象字面量 | 循环 / 条件语句 |
反模式:
<!-- 错: 尝试写多语句 -->
<span x-text="let x = 1; x * 2"></span>
<!-- 对: 单表达式 -->
<span x-text="(1) * 2"></span>
<!-- 错: 异步 -->
<span x-text="await fetch('/x')"></span>
<!-- 对: 在 x-init 异步写入 data, 然后 x-text 读 data -->
<div x-data="{ data: null }" x-init="data = await (await fetch('/x')).json()">
<span x-text="data?.field"></span>
</div>
性能 Tips
x-for超过 200 项考虑虚拟滚动 (DevExtreme DataGrid)- 复杂 getter 避免调 DOM API (会强制回流)
Alpine.reactive深层对象监听会慢, 扁平化数据- 计算密集抽到 JS 函数, 加缓存