Vue组件开发实践之scopedSlot的传递
收录待用,修改转载已取得腾讯云授权
导语
现今的前端开发都讲究模块化组件化,即把公共的交互和功能封装到一个个的组件之中,在开发整体界面的时候就能像搭积木一样快速清晰高效。在使用Vue开发我们的vhtml-ui的组件库的过程中遇到了组件嵌套组件时需要传递scopedSlot的情况,官方的文档和教程目前还没有比较明确的指引,所以摸着石头过河,调通了想要的效果。记录下来方便大家和自己。
在Vue中,为了让组件可以组合,我们使用Slot来混合父组件的内容与子组件自己的模板。这样就实现了Vue的内容分发。
Scoped Slot(作用域插槽)是在Vue 2.1引入的更进阶的功能,它是一种特殊类型的slot,用作使用一个(能够传递数据到)可重用模板替换已渲染元素。我的理解就是使用scoped slot能在插槽里自定义模板并且使用组件传递过来的context。这大大提高了组件开发的灵活性。
Select组件一期
在开发我们的select组件时很自然就用上了scoped slot这一特性。我们需要遍历数据中的选项数组,渲染成界面上的下拉选项列表。如果是比较复杂的允许自定义的list item,在组件里写死dom结构就行不通了,比如:
有了scoped slot实现很轻松:
<v-select kind="popup" :options="options4">
<template slot="listItem" scope="props">
<div>
<div><span>{{ props.item.text }}<span> | <span>{{ props.item.value }}<span></div><div>{{ props.item.area }}</div>
<div>{{ props.item.url }}</div>
</div>
</template>
</v-select>
很好,非常好,现在有一个新需求:这个列表有的时候想要脱离select使用,比如就直接展示在页面上,不需要通过下拉弹出。
select-list组件
这好办啊,作为组件开发的老司机们自然能想到把这个list独立做成一个组件,页面可以直接调用,select组件也可以在它之上再封装一层。
完美!
开干!
select-list template结构示意:
<ul class="v-select-list">
<li class="v-select-list__item" v-for="(item, index) in options">
<slot name="listItem" :item="item">
<span>{{ item.text }}</span>
</slot>
</li>
</ul>
select template结构示意:
<div class="v-select">
<v-popper>
<v-select-list>
<slot name="listItem">
</slot>
</v-select-list>
</v-popper>
</div>
然后问题来了,最里层的select-list组件并没有接收到用户自定义的scoped slot。通过查找Vue官方文档以及谷歌,也没有找到使用template方式传递scoped slot的介绍和例子。
Render函数和JSX
人总不能让尿给憋死,一条路走不通我们就看看有没有其他办法。在Vue的官方文档上有这么一句话:
“ Vue 推荐在绝大多数情况下使用 template 来创建你的 HTML。然而在一些场景中,你真的需要 JavaScript 的完全编程的能力,这就是 render 函数,它比 template 更接近编译器。”
查看文档,通过render函数确实能够传递scoped slot,以下图的方式
把scoped slot作为createElement方法的第二参数(data object)的一个属性传递到子组件中。
但是render函数的缺点就是不灵活,特别是在定义你的组件的dom结构模板的时候,如果写很多 render 函数,可能会觉得痛苦。它比较适用于外层组件仅仅是对内层组件的一次逻辑封装,而渲染的模板结构变化扩展不多的情况。
还好我们还有最后一把杀手锏--JSX。它可以让我们回到于更接近模板的语法上。具体关于JSX的使用不是本文的主题,我们可以阅读使用文档 ,学习关于 JSX 映射到 JavaScript的用法。
JSX实现上文的嵌套例子
通过参阅文档及不断地摸索,最终实现了自己想要的功能。我们直接上关键代码(select的render函数),看看有什么奥秘:
render(h) {
let directives = [{
name: 'popper',
arg: 'selector',
modifiers: { click: true }
}];
return (
<div
class={{ 'v-select': true, 'is-open': this.isPopperShown, 'is-disabled': this.disabled }}>
<v-popper
ref="selector"
placement="bottom-start"
onVisibleChange={this.togglePopper}>
<v-select-list
class="v-select__options"
onChange={this.handleChange}
multiple={this.multiple}
value={this.currentValue}
onInput={this.handleListInput}
scopedSlots={{listItem: this.$scopedSlots.listItem}} >
</v-select-list>
</v-popper>
<div
class="v-select__header"
{ ...{directives} }>
{
this.currentIndex !== -1 && this.$scopedSlots.headItem ? this.$scopedSlots.headItem(this.current)
: (<span>{this.currentText}</span>)
}
<v-icon
class="v-select__header-arrow"
name="v-arrow_dropdown">
</v-icon>
</div>
</div>
)
}
关键点:
-
在子组件的标签上通过
scopedSlots
属性可以向其传递自己的scoped slot; -
自身的scoped slot可以通过
this.$scopedSlots
对象获取,默认就是default,具名slot就是它的名字。本例为“listItem”; -
如果不在标签上传递而是需要使用表达式传递,也可以通过
this.$scopedSlots
对象。并且一个具体的scoped slot对象其实就是一个函数,其内部的scope可以在参数中传入。比如本例中的this.$scopedSlots.headItem(this.current)
JSX中对template常用点的转换
上面的介绍涵盖了基本的用法,但是我们在组件中往往会用一些不基本但常用的vue特性。我们接下来一起看看。
细心地小伙伴可能发现了上面的代码中已经出现了这些用法
directives
如果我们在组件中使用了directives,那么jsx里就不能想之前在template里那么自然的书写
v-popper:third.click
而是需要workaround:
-
Pass everything as an object via value, e.g. v-name={{ value, modifier: true }}
-
Use the raw vnode directive data format:
上面的例子中就是用了方法二。
v-model
render函数(JSX也是写在render函数中)中没有与v-model
相应的api -
你必须自己来实现相应的逻辑。即通过value属性传递值,并通过绑定input事件来响应变化。
没有template 中的v-if
和 v-for
:
这意味着我们需要在render函数或者JSX的表达式中手写if-else逻辑判断。或者如本例中使用三目表达式来实现。
这就是深入底层要付出的,尽管麻烦了一些,但你可以更灵活地控制。
希望这边文章能让我们在开发Vue组件的时候少走一些弯路,如果有大神有更好的办法或直接在template中实现传递scoped slot的功能,请多多指教!