OWL教程7 插槽
1 概述
Owl是一个基于模板的组件系统。因此,需要能够制作通用组件。例如,想象一个通用的导航条组件,它显示一个导航条,但带有一些可定制的内容。因为特定的内容只有导航条的用户知道,所以最好在使用导航条的模板中指定它:
<div>
<Navbar>
<span>Hello Owl</span>
</Navbar>
</div>
这正是插槽的工作方式!在上面的示例中,Navbar组件的用户指定了一些内容(在这里,在默认槽中)。Navbar组件可以在自己的模板中适当的位置插入该内容。需要注意的一个重要信息是,槽的内容是在父组件中渲染的,而不是在导航栏中。因此,它可以访问父组件的值和方法。
下面是导航栏组件的定义方式,使用t-slot指令,这是一个默认插槽:
<div class="navbar">
<t t-slot="default"/>
<ul>
<!-- rest of the navbar here -->
</ul>
</div>
2 命名插槽
默认插槽非常有用,但有时我们可能需要多个插槽。这就是命名插槽的作用!例如,假设我们实现了一个组件InfoBox,它显示标题和一些特定的内容。它的模板看起来像这样:
<div class="info-box">
<div class="info-box-title">
<t t-slot="title"/>
<span class="info-box-close-button" t-on-click="close">X</span>
</div>
<div class="info-box-content">
<t t-slot="content"/>
</div>
</div>
可以将它与t-set-slot指令一起使用:
<InfoBox>
<t t-set-slot="title">
Specific Title. It could be html also.
</t>
<t t-set-slot="content">
<!-- some template here, with html, events, whatever -->
</t>
</InfoBox>
3 渲染上下文
槽的内容实际上是用与其定义位置相对应的渲染上下文渲染的,而不是与其定位位置相对应的渲染上下文。这允许用户定义事件处理程序,这些事件处理程序将绑定到正确的组件(通常是槽内容的祖父组件)。
4 默认插槽
组件中所有不是命名槽的元素都将被视为默认槽内容的一部分。例如:
<div t-name="Parent">
<Child>
<span>some content</span>
</Child>
</div>
<div t-name="Child">
<t t-slot="default"/>
</div>
可以混合使用默认槽位和命名槽位:
<div>
<Child>
default content
<t t-set-slot="footer">
content for footer slot here
</t>
</Child>
</div>
5 默认的上下文
插槽可以定义一个默认的内容,以防父节点没有定义它们:
<div t-name="Parent">
<Child/>
</div>
<span t-name="Child">
<t t-slot="default">default content</t>
</span>
<!-- will be rendered as: <div><span>default content</span></div> -->
6 动态插槽
t-slot指令实际上可以使用任何表达式,使用字符串插入:
<t t-slot="{{current}}" />
这将计算当前表达式,并在t-slot指令的位置插入相应的slot。
7 插槽和props
在某种意义上,slot几乎与prop相同:它们定义了一些要传递给子组件的信息。为了能够使用它,并将其传递给子组件,Owl实际上真的定义了一个特殊的prop “slots”,其中包含给定给组件的所有插槽信息。它是这样的:
{ slotName_1: slotInfo_1, ..., slotName_m: slotInfo_m }
所以,一个组件可以像这样把它的槽传递给子组件:
<Child slots="props.slots"/>
fatux: 可以通过props 传递插槽的值, slot和props确实有相同之处,都是为了父组件给子组件传递信息. 只不过呢,一个传递个组件,一个传递给模板.
8 插槽参数
对于高级用例,可能需要将附加信息传递给插槽。这可以通过向t-set-slot指令提供额外的键/值对来实现。然后,通用组件可以在它的prop槽中读取它们。
例如,下面是如何实现Notebook组件(一个具有多个页面的组件和一个选项卡栏,它只呈现当前的活动页面,每个页面都有一个标题)。
class Notebook extends Component {
static template = xml`
<div class="notebook">
<div class="tabs">
<t t-foreach="tabNames" t-as="tab" t-key="tab_index">
<span t-att-class="{active:tab_index === activeTab}" t-on-click="() => state.activeTab=tab_index">
<t t-esc="props.slots[tab].title"/>
</span>
</t>
</div>
<div class="page">
<t t-slot="{{currentSlot}}"/>
</div>
</div>`;
setup() {
this.state = useState({ activeTab: 0 });
this.tabNames = Object.keys(this.props.slots);
}
get currentSlot() {
return this.tabNames[this.state.activeTab];
}
}
请注意如何读取每个槽的标题值。下面是如何使用这个Notebook组件:
<Notebook>
<t t-set-slot="page1" title="'Page 1'">
<div>this is in the page 1</div>
</t>
<t t-set-slot="page2" title="'Page 2'" hidden="somevalue">
<div>this is in the page 2</div>
</t>
</Notebook>
Slot参数的工作方式与普通的props类似,因此如果需要,可以使用.bind后缀来绑定函数。
9 插槽作用域
对于其他类型的高级用例,槽的内容可能依赖于特定于通用组件的一些信息。这与槽参数相反。
为了解决这类问题,可以使用t-slot-scope指令和t-set-slot指令。它定义了一个变量的名称,该变量可以访问子组件给出的所有内容:
<MyComponent>
<t t-set-slot="foo" t-slot-scope="scope">
content
<t t-esc="scope.bool"/>
<t t-esc="scope.num"/>
</t>
</MyComponent>
包含slot的子组件可以提供这样的值:
<t t-slot="foo" bool="other_var" num="5">
或者这样
<t t-slot="foo" t-props="someObject">
在使用默认插槽的情况下,你可以直接在组件本身上声明插槽作用域:
<MyComponent t-slot-scope="scope">
content
<t t-esc="scope.bool"/>
<t t-esc="scope.num"/>
</MyComponent>
Slot值的工作方式与普通的props类似,因此如果需要,可以使用.bind后缀来绑定函数。