前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Vue 组件开发实践之 scopedSlot 的传递

Vue 组件开发实践之 scopedSlot 的传递

原创
作者头像
谭伟华
修改2017-06-19 19:02:53
12K1
修改2017-06-19 19:02:53
举报
文章被收录于专栏:谭伟华的专栏

导语

现今的前端开发都讲究模块化组件化,即把公共的交互和功能封装到一个个的组件之中,在开发整体界面的时候就能像搭积木一样快速清晰高效。在使用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实现很轻松:

代码语言:javascript
复制

    <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结构示意:

代码语言:javascript
复制
    <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结构示意:

代码语言:javascript
复制
    <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函数),看看有什么奥秘:

代码语言:javascript
复制
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里那么自然的书写

代码语言:javascript
复制
v-popper:third.click

而是需要workaround:

  1. Pass everything as an object via value, e.g. v-name={{ value, modifier: true }}
  2. Use the raw vnode directive data format:

上面的例子中就是用了方法二。

v-model

render函数(JSX也是写在render函数中)中没有与v-model相应的api - 你必须自己来实现相应的逻辑。即通过value属性传递值,并通过绑定input事件来响应变化。

没有template 中的v-ifv-for:

这意味着我们需要在render函数或者JSX的表达式中手写if-else逻辑判断。或者如本例中使用三目表达式来实现。

这就是深入底层要付出的,尽管麻烦了一些,但你可以更灵活地控制。

希望这边文章能让我们在开发Vue组件的时候少走一些弯路,如果有大神有更好的办法或直接在template中实现传递scoped slot的功能,请多多指教!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 导语
  • Select组件一期
  • select-list组件
  • Render函数和JSX
  • JSX实现上文的嵌套例子
  • 关键点:
  • JSX中对template常用点的转换
    • directives
      • v-model
        • 没有template 中的v-if和 v-for:
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档