Vue & Bootstrap 結合學習筆記(二)

本文主要描述不才在學習Vue和Bootstrap中遇到的關於插槽的問題及解決方案

插槽理解

vue官網和不少熱心朋友又不少關於默認插槽,具名插槽,插槽做用域相關的解釋,我就不重複搬過來佔用篇幅了,這裏簡單的談談我我的的淺見。html

  • 插槽就是預留位置
    插槽其實就是定義組件時預留的一些位置,將使用組件時組件內額外的一些標籤寫入到對應的預留位置就是插槽的做用了。
  • 插槽適用於不固定內容的組件
    正是由於沒法預見組件內部全部可能的標籤或內容,因此乾脆留一個空缺,全權交給使用者,想填啥就用啥。
  • 具名插槽適用於具備必定結構且有多處不固定內容的組件
    此時在定義組件時其實就是預留了多個空缺位置且分別命名(若是隻有一個固然也能夠採用具名插槽,但確定不如默認插槽,只會徒增配置成本)。而後在使用時將內容分紅多塊分別命名成預留空缺位置的名字。

組件理解

爲何要定義組件?
答:定義組件是一種封裝的形式,使用最簡單的標籤及屬性配置表達一大段比較豐富的結構效果及一些數據和事件。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和具名namenode

動態增長$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插槽的處理辦法略顯暴力,但的確解決了我遇到的問題,各位大拿有更好的經驗還請不吝賜教,拜謝!
相關文章
相關標籤/搜索