Skip to content

🦉 Templates 🦉

Overview

Owl 模板采用 QWeb 规范进行描述。该规范基于 XML 格式,主要用于生成 HTML。在 OWL 框架中,QWeb 模板会被编译成生成 HTML 虚拟 DOM 的函数。由于 Owl 是一个实时组件系统,因此它还支持一些特定的指令,比如 t-on,用于增强模板的交互性。

xml
<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) 会被转换为:

js
context["a"] + context["b"].c(context["d"]);

表达式规则:

  1. 表达式必须是返回值的简单表达式,不能是语句:

    ✅ 合法:

    xml
    <div><p t-if="1 + 2 === 3">ok</p></div>

    ❌ 非法:

    xml
    <div><p t-if="console.log(1)">NOT valid</p></div>
  2. 表达式可以使用渲染上下文中的内容(通常是组件的属性):

    xml
    <p t-if="user.birthday === today()">Happy birthday!</p>
  3. 可使用特殊关键词替代某些操作符,以保持 XML 合法性:

    关键词替代符号
    and&&
    or||
    gt>
    gte>=
    lt<
    lte<=

    例如:

    xml
    <div><p t-if="10 + 2 gt 5">ok</p></div>

静态 HTML 节点(Static Html Nodes)

普通的 HTML 标签会直接原样渲染:

xml
<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 时不转义)。

    js
    const { markup } = owl;
    value1 = "<div>text1</div>";
    value2 = markup("<div>text2</div>");

    模板:

    xml
    <t t-out="value1"/> <!-- 被转义 -->
    <t t-out="value2"/> <!-- 直接插入 HTML -->
  • markup 也可作为标签函数,自动对插值部分转义:

    js
    markup`<b>${maliciousInput}</b>`; // 会安全转义内容

设置变量(Setting Variables)

通过 t-set 创建变量:

  1. 使用 t-value 指定表达式:

    xml
    <t t-set="foo" t-value="2 + 1"/>
    <t t-esc="foo"/> <!-- 输出 3 -->
  2. 使用标签体作为值:

    xml
    <t t-set="foo">
        <li>ok</li>
    </t>
    <t t-esc="foo"/> <!-- 输出转义后的 HTML -->

变量作用域为词法作用域,可嵌套、遮蔽。


条件语句(Conditionals)

t-if 根据条件渲染内容:

xml
<t t-if="condition"><p>ok</p></t>

等效于:

xml
<p t-if="condition">ok</p>

还支持 t-elift-else

xml
<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-foreacht-as 迭代集合:

xml
<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 调用子模板:

xml
<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:在控制台打印表达式结果
xml
<t t-set="foo" t-value="42"/>
<t t-log="foo"/> <!-- 控制台输出 42 -->

自定义指令(Custom Directives)

可注册自定义指令:

js
new App(..., {
  customDirectives: {
    test_directive: (el, value) => {
      el.setAttribute("t-on-click", value);
    }
  }
});

模板中使用:

xml
<div t-custom-test_directive="click"/>

将被替换为:

xml
<div t-on-click="click"/>

如果你需要示例代码、练习题或解释某个部分的用法,我可以继续补充。

以下是这段文档的中文翻译:


片段(Fragments)

Owl 2 支持包含任意数量根元素的模板,甚至只包含一个文本节点也可以。因此,以下模板都是合法的:

xml
hello owl. This is just a text node!
xml
<div>hello</div>
xml
<div>hello</div>
<div>ola</div>
xml
<div t-if="someCondition"><SomeChildComponent/></div>
xml
<t t-if="someCondition"><SomeChildComponent/></t>

内联模板(Inline templates)

大多数真实的应用程序会在 XML 文件中定义模板,以便利用 XML 生态系统,并进行诸如翻译等额外的处理。然而,在某些情况下,能够直接内联定义模板会更加方便。为此,可以使用 xml 帮助函数:

js
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 图形:

js
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 依赖一个启发式方法:如果某个标签是 svggpath,那么它将被视为 SVG 元素。实际上,这意味着每个组件或子模板(用 t-call 引入)都应以这些标签之一作为根元素。


限制(Restrictions)

请注意,Owl 模板中禁止使用以 block- 开头的标签名或属性名。此限制是为了避免与 Owl 内部代码的命名冲突。

xml
<div><block-1>this will not be accepted by Owl</block-1></div>

如果你还需要翻译更多关于 Owl 的内容或相关例子,我可以继续帮助你。

本站内容仅供学习与参考