🦉 Templates 🦉
Overview
Owl 模板采用 QWeb 规范进行描述。该规范基于 XML 格式,主要用于生成 HTML。在 OWL 框架中,QWeb 模板会被编译成生成 HTML 虚拟 DOM 的函数。由于 Owl 是一个实时组件系统,因此它还支持一些特定的指令,比如 t-on
,用于增强模板的交互性。
<div>
<span t-if="somecondition">Some string</span>
<ul t-else="">
<li t-foreach="messages" t-as="message">
<t t-esc="message"/>
</li>
</ul>
</div>
模板指令以以 t-
为前缀的 XML 属性形式指定,例如用于条件判断的 t-if
,而元素及其他属性则会被直接渲染。
为了避免渲染元素本身,还提供了占位元素 <t>
,它会执行其指令,但自身不会生成任何输出。
本节将介绍模板语言,包括其 Owl 特有的扩展。
指令
以下是所有标准 QWeb 指令的参考列表:
名称 | 描述 |
---|---|
t-esc | 安全地输出一个值 |
t-out | 输出一个值,可能不会转义 |
t-set , t-value | 设置变量 |
t-if , t-elif , t-else | 条件渲染 |
t-foreach , t-as | 循环 |
t-att , t-attf-* , t-att-* | 动态属性 |
t-call | 渲染子模板 |
t-debug , t-log | 调试 |
t-translation | 禁用节点的翻译 |
t-translation-context | 节点内翻译的上下文 |
t-translation-context-* | 特定节点属性的翻译上下文 |
Owl 的组件系统还需要额外的指令来表达各种需求。以下是所有 Owl 特有指令的列表:
名称 | 描述 |
---|---|
t-component , t-props | 定义子组件 |
t-ref | 为 DOM 节点或子组件设置引用 |
t-key | 定义 key(用于辅助虚拟 DOM 对比) |
t-on-* | 事件处理 |
t-portal | 传送门 |
t-slot , t-set-slot , t-slot-scope | 渲染插槽 |
t-model | 表单输入绑定 |
t-tag | 渲染具有动态标签名的节点 |
t-custom-* | 使用自定义指令渲染节点 |
QWeb 模板参考
空白字符(White Spaces)
模板中的空白字符处理方式特殊:
- 连续空白字符会被压缩为一个空格;
- 仅包含换行符的空白文本节点会被忽略;
- 上述规则在
<pre>
标签中不适用。
表达式求值(Expression Evaluation)
QWeb 表达式是编译时处理的字符串。每个变量将被替换为在上下文(通常是组件)中的查找:
例如:a + b.c(d)
会被转换为:
context["a"] + context["b"].c(context["d"]);
表达式规则:
表达式必须是返回值的简单表达式,不能是语句:
✅ 合法:
xml<div><p t-if="1 + 2 === 3">ok</p></div>
❌ 非法:
xml<div><p t-if="console.log(1)">NOT valid</p></div>
表达式可以使用渲染上下文中的内容(通常是组件的属性):
xml<p t-if="user.birthday === today()">Happy birthday!</p>
可使用特殊关键词替代某些操作符,以保持 XML 合法性:
关键词 替代符号 and &&
or ||
gt >
gte >=
lt <
lte <=
例如:
xml<div><p t-if="10 + 2 gt 5">ok</p></div>
静态 HTML 节点(Static Html Nodes)
普通的 HTML 标签会直接原样渲染:
<div>hello</div> <!-- 直接输出为 <div>hello</div> -->
输出数据(Outputting Data)
t-esc
:用于输出动态表达式的值,自动转义以确保安全。xml<p><t t-esc="value"/></p> <!-- value 为 42 时,输出 <p>42</p> -->
t-out
:几乎与t-esc
相同,但不一定转义(仅在明确标记为markup
时不转义)。jsconst { markup } = owl; value1 = "<div>text1</div>"; value2 = markup("<div>text2</div>");
模板:
xml<t t-out="value1"/> <!-- 被转义 --> <t t-out="value2"/> <!-- 直接插入 HTML -->
markup
也可作为标签函数,自动对插值部分转义:jsmarkup`<b>${maliciousInput}</b>`; // 会安全转义内容
设置变量(Setting Variables)
通过 t-set
创建变量:
使用
t-value
指定表达式:xml<t t-set="foo" t-value="2 + 1"/> <t t-esc="foo"/> <!-- 输出 3 -->
使用标签体作为值:
xml<t t-set="foo"> <li>ok</li> </t> <t t-esc="foo"/> <!-- 输出转义后的 HTML -->
变量作用域为词法作用域,可嵌套、遮蔽。
条件语句(Conditionals)
t-if
根据条件渲染内容:
<t t-if="condition"><p>ok</p></t>
等效于:
<p t-if="condition">ok</p>
还支持 t-elif
和 t-else
:
<p t-if="user.birthday == today()">Happy birthday!</p>
<p t-elif="user.login == 'root'">Welcome master!</p>
<p t-else="">Welcome!</p>
动态属性(Dynamic Attributes)
使用
t-att-
绑定动态属性:xml<div t-att-data-id="id"/> <!-- id 为 32 时,输出 <div data-id="32"></div> -->
如果值为 falsy,不会设置属性:
xml<div t-att-disabled="false"/> <!-- 输出 <div></div> -->
使用
t-attf-
插值组合字面量和变量:xml<div t-attf-foo="a {{v1}} is #{v2}"/> <!-- 插值替换 -->
使用
t-att
设置完整属性对象:xml<div t-att="{'a': 1, 'b': 2}"/> <!-- 输出 <div a="1" b="2"></div> -->
动态类(Dynamic class attribute)
使用
t-att-class
传入对象控制类名:xml<div t-att-class="{'a': true, 'b': false}"/> <!-- 输出 <div class="a"></div> -->
可与普通 class 属性合并:
xml<div class="base" t-att-class="{'active': true}"/> <!-- 输出 <div class="base active"></div> -->
动态标签名(Dynamic tag names)
使用
t-tag
动态设置 HTML 标签名:xml<t t-tag="tag"><span>content</span></t>
若
tag = 'div'
,结果为<div><span>content</span></div>
循环(Loops)
使用 t-foreach
和 t-as
迭代集合:
<t t-foreach="[1,2,3]" t-as="i" t-key="i">
<p><t t-esc="i"/></p>
</t>
必须提供唯一的
t-key
用于识别节点;可迭代数组、对象、Map;
提供辅助变量:
变量名 说明 $as_value
当前值(对象时为值) $as_index
当前索引(从 0 开始) $as_first
是否为第一个元素 $as_last
是否为最后一个元素
作用域局限于循环内部。
子模板(Sub Templates)
使用 t-call
调用子模板:
<t t-call="other-template"/>
子模板使用调用者上下文;
可传入正文作为特殊变量
0
:xml<t t-call="template"> <em>内容</em> </t>
子模板中使用
<t t-raw="0"/>
渲染内容。使用
t-call-context
指定上下文对象:xml<t t-call="tpl" t-call-context="myObj"/>
可使用插值动态调用模板名:
xml<t t-call="{{templateName}}"/>
调试(Debugging)
t-debug
:在渲染时插入debugger
语句t-log
:在控制台打印表达式结果
<t t-set="foo" t-value="42"/>
<t t-log="foo"/> <!-- 控制台输出 42 -->
自定义指令(Custom Directives)
可注册自定义指令:
new App(..., {
customDirectives: {
test_directive: (el, value) => {
el.setAttribute("t-on-click", value);
}
}
});
模板中使用:
<div t-custom-test_directive="click"/>
将被替换为:
<div t-on-click="click"/>
如果你需要示例代码、练习题或解释某个部分的用法,我可以继续补充。
以下是这段文档的中文翻译:
片段(Fragments)
Owl 2 支持包含任意数量根元素的模板,甚至只包含一个文本节点也可以。因此,以下模板都是合法的:
hello owl. This is just a text node!
<div>hello</div>
<div>hello</div>
<div>ola</div>
<div t-if="someCondition"><SomeChildComponent/></div>
<t t-if="someCondition"><SomeChildComponent/></t>
内联模板(Inline templates)
大多数真实的应用程序会在 XML 文件中定义模板,以便利用 XML 生态系统,并进行诸如翻译等额外的处理。然而,在某些情况下,能够直接内联定义模板会更加方便。为此,可以使用 xml
帮助函数:
const { Component, xml } = owl;
class MyComponent extends Component {
static template = xml`
<div>
<span t-if="somecondition">text</span>
<button t-on-click="someMethod">Click</button>
</div>
`;
...
}
mount(MyComponent, document.body);
该函数会生成一个唯一的字符串 ID,并在 Owl 的内部系统中注册该模板,然后返回该 ID。
渲染 SVG(Rendering svg)
Owl 组件可以用来生成动态的 SVG 图形:
class Node extends Component {
static template = xml`
<g>
<circle t-att-cx="props.x" t-att-cy="props.y" r="4" fill="black"/>
<text t-att-x="props.x - 5" t-att-y="props.y + 18"><t t-esc="props.node.label"/></text>
<t t-set="childx" t-value="props.x + 100"/>
<t t-set="height" t-value="props.height/(props.node.children || []).length"/>
<t t-foreach="props.node.children || []" t-as="child">
<t t-set="childy" t-value="props.y + child_index*height"/>
<line t-att-x1="props.x" t-att-y1="props.y" t-att-x2="childx" t-att-y2="childy" stroke="black" />
<Node x="childx" y="childy" node="child" height="height"/>
</t>
</g>
`;
static components = { Node };
}
class RootNode extends Component {
static template = xml`
<svg height="180">
<Node node="graph" x="10" y="20" height="180"/>
</svg>
`;
static components = { Node };
graph = {
label: "a",
children: [
{ label: "b" },
{ label: "c", children: [{ label: "d" }, { label: "e" }] },
{ label: "f", children: [{ label: "g" }] },
],
};
}
这个 RootNode
组件将展示由 graph
属性描述的图的实时 SVG 可视化结构。请注意,这里是一个递归结构:Node
组件会调用自身作为子组件。
重要提示: Owl 需要正确设置每个 SVG 元素的命名空间。由于 Owl 是分别编译每个模板的,它无法轻易判断某个模板是否应包含在 SVG 命名空间中。因此,Owl 依赖一个启发式方法:如果某个标签是 svg
、g
或 path
,那么它将被视为 SVG 元素。实际上,这意味着每个组件或子模板(用 t-call
引入)都应以这些标签之一作为根元素。
限制(Restrictions)
请注意,Owl 模板中禁止使用以 block-
开头的标签名或属性名。此限制是为了避免与 Owl 内部代码的命名冲突。
<div><block-1>this will not be accepted by Owl</block-1></div>
如果你还需要翻译更多关于 Owl 的内容或相关例子,我可以继续帮助你。