• 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>&nbsp;|&nbsp;<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:

    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的功能,请多多指教!


    原文链接:https://www.qcloud.com/community/article/693017

  • 相关阅读:
    如何提高工作效率,重复利用时间
    好记性不如烂笔头
    如何应对面试中关于“测试框架”的问题
    通宵修复BUG的思考
    工作方法的思考
    别认为那是一件简单的事情
    开发人员需要熟悉缺陷管理办法
    不了解系统功能的思考
    如何布置任务
    事事有回音
  • 原文地址:https://www.cnblogs.com/liuliliuli2017/p/6838272.html
Copyright © 2020-2023  润新知