Vue & Bootstrap 结合学习笔记(二)

本文主要描述不才在学习Vue和Bootstrap中遇到的关于插槽的问题及解决方案

插槽理解

vue官网和很多热心朋友又很多关于默认插槽,具名插槽,插槽作用域相关的解释,我就不重复搬过来占用篇幅了,这里简单的谈谈我个人的浅见。

  • 插槽就是预留位置
    插槽其实就是定义组件时预留的一些位置,将使用组件时组件内额外的一些标签写入到对应的预留位置就是插槽的作用了。
  • 插槽适用于不固定内容的组件
    正是因为无法预见组件内部所有可能的标签或内容,所以干脆留一个空缺,全权交给使用者,想填啥就用啥。
  • 具名插槽适用于具有一定结构且有多处不固定内容的组件
    此时在定义组件时其实就是预留了多个空缺位置且分别命名(如果只有一个当然也可以采用具名插槽,但肯定不如默认插槽,只会徒增配置成本)。然后在使用时将内容分成多块分别命名成预留空缺位置的名字。

组件理解

为什么要定义组件?
答:定义组件是一种封装的形式,使用最简单的标签及属性配置表达一大段比较丰富的结构效果及一些数据和事件。

Bootstrap组件-Collapse(Accordion example)

官网HTML代码

<div id="accordion">
  <div class="card">
    <div class="card-header" id="headingOne">
      <h5 class="mb-0">
        <button class="btn btn-link" data-toggle="collapse" data-target="#collapseOne" aria-expanded="true" aria-controls="collapseOne">
          Collapsible Group Item #1
        </button>
      </h5>
    </div>

    <div id="collapseOne" class="collapse show" aria-labelledby="headingOne" data-parent="#accordion">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
  </div>
  <div class="card">
    <div class="card-header" id="headingTwo">
      <h5 class="mb-0">
        <button class="btn btn-link collapsed" data-toggle="collapse" data-target="#collapseTwo" aria-expanded="false" aria-controls="collapseTwo">
          Collapsible Group Item #2
        </button>
      </h5>
    </div>
    <div id="collapseTwo" class="collapse" aria-labelledby="headingTwo" data-parent="#accordion">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
  </div>
  <div class="card">
    <div class="card-header" id="headingThree">
      <h5 class="mb-0">
        <button class="btn btn-link collapsed" data-toggle="collapse" data-target="#collapseThree" aria-expanded="false" aria-controls="collapseThree">
          Collapsible Group Item #3
        </button>
      </h5>
    </div>
    <div id="collapseThree" class="collapse" aria-labelledby="headingThree" data-parent="#accordion">
      <div class="card-body">
        Anim pariatur cliche reprehenderit, enim eiusmod high life accusamus terry richardson ad squid. 3 wolf moon officia aute, non cupidatat skateboard dolor brunch. Food truck quinoa nesciunt laborum eiusmod. Brunch 3 wolf moon tempor, sunt aliqua put a bird on it squid single-origin coffee nulla assumenda shoreditch et. Nihil anim keffiyeh helvetica, craft beer labore wes anderson cred nesciunt sapiente ea proident. Ad vegan excepteur butcher vice lomo. Leggings occaecat craft beer farm-to-table, raw denim aesthetic synth nesciunt you probably haven't heard of them accusamus labore sustainable VHS.
      </div>
    </div>
  </div>
</div>
  • 该示例代码比较长
  • 结构是很有规律的,很容易发现几个共同的结构:card, card-header, card-body…
  • 文案是具体的展示信息,也是我们希望组件能够配置的

最精简的组件配置

<widget-collapse>
    <div header="Collapsible Group Item #1">
        Anim pariatur ...
    </div>
    <div header="Collapsible Group Item #2">
        Anim pariatur ...
    </div>
    <div header="Collapsible Group Item #3">
        Anim pariatur ...
    </div>
</widget-collapse>
  • header一般文本就够用了,所以直接配置在标签属性上
  • content, 如’Anim pariatur …’我们预想其可能是其他的一个或多个组件甚至大段html(会有标签),此时放到属性内就不合适了,配置起来超级麻烦

组件封装

  • 获取header简单。遍历$children从其attrs对象中可以读取到,放到data中的一个数组里,采用v-for指令即可很容易渲染出来
  • 获取content不容易。$children都是虚拟DOM,反向变成html的API没找到;再者就算找到了,同header一样遍历渲染出来,也只是当成字符串渲染出来,加上v-html指令,标准的HTML标签倒是确实渲染出来了,可已定义的组件标签未能正确解析渲染(不知道vue有没有相应的API方法解决)。
  • 插槽<slot></slot>刚好可以解决内容中的标签解析问题,但它会将所有的内容(上例中3个div)都解析到一起无法拆分
  • 具名插槽<slot name="..."></slot>可以解决内容无法拆分的问题。那我们就把内容中的每一个都标一个name, name取值要满足内容个数不定的条件,所以最好采用和序号有关的,刚好可以在v-for指令中匹配到。
  • 问题解决!

组件定义

Vue.component('widget-collapse', {
    template: `<div>
        <div class="card" v-for="(item, index) in vitems">
            <div class="card-header">
                <h5 class="mb-0">
                    <button class="btn btn-link" data-toggle="collapse">{{item.header}}</button>
                </h5>
            </div>

            <div :class="['collapse']">
                <div class="card-body">
                    <slot :name="index"></slot>
                </div>
            </div>
        </div>
    </div>`,
    data() {
        let children = this.$slots, items = [];
        for (let i in children) {
            let node = children[i][0];
            if (node.tag) {
                items[i] = ({
                    header: VTool.attr(node, 'header') || ('Item ' + items.length),
                })
            }
        }
        return {
            vitems: items
        }
    }
})

组件使用

<widget-collapse>
    <div header="Collapsible Group Item #1" slot="0">
        Anim pariatur ...
    </div>
    <div header="Collapsible Group Item #2" slot="1">
        Anim pariatur ...
    </div>
    <div header="Collapsible Group Item #3" slot="2">
        Anim pariatur ...
    </div>
</widget-collapse>

组件优化

使用时每个节点都写一个slot太累赘,容易写错,删除某个节点或调整节点顺序后更改麻烦,能否程序动态帮忙添加该slot属性?
答:未找到对应API,但对比默认与具名插槽的$slots数据对象发现key分别为default和具名name

动态增加$slots属性值

Vue.component('widget-collapse', {
    template: `<div>
            <div class="card" v-for="(item, index) in vitems">
                <div class="card-header">
                    <h5 class="mb-0">
                        <button class="btn btn-link" data-toggle="collapse">{{item.header}}</button>
                    </h5>
                </div>

                <div :class="['collapse']">
                    <div class="card-body">
                        <slot :name="item.id"></slot>
                    </div>
                </div>
            </div>
        </div>`,
    data() {
        let children = this.$slots.default, items = [];
        for (let i = 0, len = children.length; i < len; i++) {
            let node = children[i];
            if (node.tag) {
                let id = VTool.random();
                this.$slots[id] = node;
                items.push({
                    id: id,
                    header: VTool.attr(node, 'header') || ('Item ' + items.length),
                })
            }
        }
        return {
            vitems: items
        }
    }
})

删除使用时的插槽命名

<widget-collapse>
    <div header="Collapsible Group Item #1">
        Anim pariatur ...
    </div>
    <div header="Collapsible Group Item #2">
        Anim pariatur ...
    </div>
    <div header="Collapsible Group Item #3">
        Anim pariatur ...
    </div>
</widget-collapse>

同步更新

  • 以上设置基本初始化是没有任何问题了,但在vue对象更新时会更改$slots对象
    (经跟踪代码查找到updateChildComponent方法内在更新下层组件时执行了vm.$slots = resolveSlots(renderChildren, parentVnode.context);覆盖了之前缩处理过的$slots,且会在vm._render方法中重新渲染,且在此之前没有公布任何事件,那就暴力覆盖重写了)
Vue.component('widget-collapse', {
    ...
    created() {
        let _render = this._render;
        this._render = function() {
            let $slots = this.$slots;
            for (let name in $slots) {
                if (name !== 'default') {
                    return _render.apply(this, arguments);
                }
            }
            // 此时肯定是重新new的$slots, 重写对应关系
            let children = this.$slots.default, items = [];
            for (let i = 0, len = children.length; i < len; i++) {
                let node = children[i];
                if (node.tag) {
                    let id = VTool.random();
                    this.$slots[id] = node;
                    items.push({
                        id: id,
                        header: VTool.attr(node, 'header') || ('Item ' + items.length),
                    })
                }
            }
            this.vitems = items;
            return _render.apply(this, arguments);
        }
    },
    ...
})

本文对于vue插槽的处理办法略显暴力,但的确解决了我遇到的问题,各位大拿有更好的经验还请不吝赐教,拜谢!

    原文作者:石侠
    原文地址: https://segmentfault.com/a/1190000015351130
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞