LOGO

如何实现元素的显示/隐藏?

编辑本文

通过s-if指令,我们可以为元素指定条件。只有当条件成立时元素才会渲染,否则元素不会被加载。

s-if无法实现这样的需求:我们需要在符合条件的情况下显示某元素,条件不满足时,元素在页面中隐藏,但依然被挂载到 DOM 。这个时候,元素的展现用 CSS 控制更为合适。

这一需求的本质可以归纳为:如何根据条件实现元素的显示/隐藏。

如何处理

San 提供在视图模板中进行样式处理的方案,详见教程。你可以用不同的 class 控制样式,也可以用 inline 样式实现。

1. 用 class 控制元素的显示与隐藏

1
2
3
4
<!-- template -->
<div>
<ul class="list{{isHidden ? ' list-hidden' : ' list-visible'}}"></ul>
</div>

注意, class 属性有多个类名时,需要为第一个以后的类名加上空格。

codepen 演示如下:

See the Pen 根据条件添加不同样式-用class控制 by MinZhou (@Mona_) on CodePen.

CSS 控制着样式的展现,所以 DOM 始终都存在页面节点树中。你可以打开控制台看看。

2. 用内联样式控制元素的隐藏与显示

1
2
3
4
<!-- template -->
<div>
<ul style="display: {{isHidden ? 'none' : 'block'}}">visible</ul>
</div>

See the Pen 根据条件添加不同样式-用内联样式控制 by MinZhou (@Mona_) on CodePen.

有时候数据可能并不存在,所以把样式名包含在插值中更为可靠。

1
2
3
4
<!-- template -->
<div>
<ul style="{{isHidden === false ? 'display: none' : 'display: block'}}">visible</ul>
</div>

3. 使用计算属性

前面的两种方案都可以通过使用计算属性,将判断逻辑从模板中解耦出来,以便更好的应对可能变得更为复杂的需求。下面是基于 class 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
san.defineComponent({
template: `
<div>
<ul class="{{ulClass}}"></ul>
</div>
`,
computed: {
ulClass() {
const isHidden = this.data.get('isHidden');
if (isHidden) {
return 'list list-hidden';
}
return 'list list-visible';
}
}
})

codepen 演示如下:

See the Pen 基于 computed 的元素显示隐藏 by LeuisKen (@LeuisKen) on CodePen.

4. 使用 filter

filter 也可以用于对 class 和 style 进行处理,解耦的效果和 computed 类似,其特点是能够显式地声明属性值与数据的依赖关系。下面是基于 class 的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
san.defineComponent({
template: `
<div>
<ul class="{{isHidden | handleHidden}}"></ul>
</div>
`,
filters: {
handleHidden(isHidden) {
if (isHidden) {
return 'list list-hidden';
}
return 'list list-visible';
}
}
})

codepen 演示如下:

See the Pen 基于 filter 的元素显示隐藏 by LeuisKen (@LeuisKen) on CodePen.

我们可以很明显地看出,class 是由 isHidden 控制的。

这里要额外注意的是,如果和 class 关联的有多个 data ,用 filter 的方法可能会有一些问题,比如我在下面的例子中实现了一个tab组件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
san.defineComponent({
template: `
<div class="tab">
<div
s-for="tab in tabs"
class="{{tab.value | mapActive}}"
on-click="tabChange(tab.value)"
>
{{tab.name}}
</div>
</div>
`,
initData() {
return {
active: '',
tabs: [
{
name: '第一项',
value: 'one'
},
{
name: '第二项',
value: 'two'
}
]
};
},
tabChange(value) {
this.data.set('active', value);
},
filters: {
mapActive(value) {
const active = this.data.get('active');
const classStr = 'sm-tab-item';
if (value === active) {
return classStr + ' active';
}
return classStr;
}
}
});

codepen 演示如下:

See the Pen 没有显式声明依赖的tab bug演示 by LeuisKen (@LeuisKen) on CodePen.

此处当我在点击 tab 的时候,虽然 active 能够正常更新,但是视图不会引起变化,因为 San 的依赖收集机制不认为 active 的修改会影响到视图,因此需要我们在模板中显式声明对 active 的依赖,参考如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
san.defineComponent({
// 将下面的 mapActive 改成 mapActive(active),显示声明视图对 active 的依赖
template: `
<div class="tab">
<div
s-for="tab in tabs"
class="{{tab.value | mapActive(active)}}"
on-click="tabChange(tab.value)"
>
{{tab.name}}
</div>
</div>
`,
initData() {
return {
active: '',
tabs: [
{
name: '第一项',
value: 'one'
},
{
name: '第二项',
value: 'two'
}
]
};
},
tabChange(value) {
this.data.set('active', value);
this.fire('change', value);
},
filters: {
// 这里就不需要通过 this.data.get('active') 拿到 active 了
mapActive(value, active) {
const classStr = 'sm-tab-item';
if (value === active) {
return classStr + ' active';
}
return classStr;
}
}
});

codepen 演示如下:

See the Pen 显式声明依赖的tab演示 by LeuisKen (@LeuisKen) on CodePen.

通过在模板中显示声明视图对 active 的依赖, San 就能正常更新视图了。这也是为什么我会在一开始说 filter 的特点是能够显式地声明属性值与数据的依赖关系。

结语

隐藏和显示是开发中较为常见的需求,还有一些其他的样式切换需求,使用以上两种方法都可以轻松实现。

总结一下,如果你要控制元素的渲染与否(是否添加到节点树),你需要使用s-if指令;如果你仅仅只想控制 DOM 节点的样式,比如元素的显示/隐藏样式,请使用数据控制 class 或内联样式。