1. 程式人生 > >vue.js 精學組件記錄

vue.js 精學組件記錄

val 共存 str event mes 沒有 所有 性能 spa

組件需要註冊後才可以使用。

Vue.component(‘my-component‘,{

  template:‘<div>這是組件內容</div>‘  

});

局部註冊組件

var Child = {
  template:‘<div>局部註冊組件內容</div>‘  
}

var app = new Vue({
   el:"#app",
   components: {
       ‘my-component‘:Child 
    }  
})    

Vue組件模板在某些情況下會受到限制,比如table,ul,ol.select等,這時可以類似這樣:

<table>
    <tbody is="my-component"></tbody>
</table>

還可使用數據綁定,但data必須是函數:

Vue.component(‘my-component‘,{
     template:‘<div>{{message}}</div>‘,
     data:function(){
         return {
             message:‘局部註冊內容,data方法return‘
         }
     }
})

使用props傳遞數據

<my-component message="來自父組件的信息"></my-component>
Vue.component(‘my-component‘,{
       props:[‘message‘],
       template:‘<div>{{message}}</div>‘
})

父組件動態數據:

<input type="text" v-model="message"/>
<my-component :message="message"></my-component>
Vue.component(‘my-component‘,{
       props:[
‘message‘], template:‘<div>{{message}}</div>‘ }) var app = new Vue({ el:"#app", data:{ message:‘‘ } })

當輸入框輸入時,子組件接收到的props "message" 也會實時響應,並更新組件模板。

註意:

如果直接傳遞數字,布爾值,數組,對象,而不是用v-bind,傳遞的是字符串。

<my-component message="[1,2,3]"></my-component>
<my-component :message="[1,2,3]"></my-component>
Vue.component(‘my-component‘,{
       props:[‘message‘],
       template:‘<div>{{message.length}}</div>‘
})

此時上一個會輸出字符串的長度7,下面才是輸出數組的長度3。

單向數據流

父組件傳遞初始值進來,子組件作為初始值保存,在自己的作用域下可隨意修改。

<div id="app">
        <my-component :init-count="1"></my-component>
    </div>


    <script type="text/javascript">

        Vue.component(‘my-component‘,{
            props:[‘initCount‘],
            template:‘<div>{{count}}</div>‘,
            data:function(){
                return {
                    count:this.initCount+1
                }
            }
        })


        var app = new Vue({
            el:"#app",
            data:{
                message:‘‘
            }

        })
    </script>
<div id="app">
        <my-component :width="100"></my-component>
    </div>


    <script type="text/javascript">

        Vue.component(‘my-component‘,{
            props:[‘width‘],
            template:‘<div :style="style">組件內容</div>‘,
            computed:{
                style:function(){
                    return {
                        width:this.width+"px",
                        background:‘red‘
                    }
                }
            }
        })


        var app = new Vue({
            el:"#app",
            data:{
                message:‘‘
            }

        })
    </script>

子組件傳遞數值到父組件:

<div id="app">
        <p>總數:{{total}}</p>
        <my-component @increase="handleGetTotal" @reduce="handleGetTotal"></my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            template:‘<div><button @click="handleIncrease">+1</button><button @click="handleReduce">-1</button></div>‘,
            data:function(){
                return {
                    counter: 0
                }
            },
            methods:{
                handleIncrease:function(){
                    this.counter++;
                    this.$emit(‘increase‘,this.counter);
                },
                handleReduce:function(){
                    this.counter--;
                    this.$emit(‘reduce‘,this.counter);
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                total:0
            },
            methods:{
                handleGetTotal:function(total){
                    this.total=total
                    console.log(total)
                }
            }

        });

在子組件定義兩個事件,改變自身數據的同時使用$emit來觸發父組件的自定義事件名稱,從而觸發對應的方法,再把改變的數據通過方法傳到父組件的方法,從而改變父組件的數據。

使用v-model

<div id="app">
        <p>總數:{{total}}</p>
        <my-component v-model="total"></my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            template:‘<div><button @click="handleIncrease">+1</button></div>‘,
            data:function(){
                return {
                    counter: 0
                }
            },
            methods:{
                handleIncrease:function(){
                    this.counter++;
                    this.$emit(‘input‘,this.counter);
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                total:0
            }

        });
    </script>

這次區別是$emit通知的是特殊的input,但是並沒有在組件上使用@input,這是因為v-model,這也可以稱作是一個語法糖。

因為上面的示例可以間接實現:

<my-component @input="total"></my-component>

v-model還可以用來創建自定義的表單輸入組件,進行數據雙向綁定:

<div id="app">
        <p>總數:{{total}}</p>
        <my-component v-model="total"></my-component>
        <button @click="handleReduce">-1</button>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            props:[‘value‘],
            template:‘<input :value="value" @input="updateValue" />‘,
            methods:{
                updateValue:function(event){
                    this.$emit(‘input‘,event.target.value);
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                total:0
            },
            methods:{
                handleReduce:function(){
                    this.total--;
                }
            }

        });
    </script>

子組件input改變value值後出發updateValue,通過$emit觸發特殊的input事件,傳值輸入的value值,改掉父組件的total,父組件的handleReduce方法改變total後,由於是雙向綁定,所以子組件的value值也隨著改變。

這需要滿足兩個條件:

1.接收一個props的value值,

2.更新value觸發特殊的input事件

非父子組件通信

在vue1.x中是采用$dispatch()和$broadcast()這兩個方法。$dispatch()由於向上級派發事件,只要是它的父級,都可在vue實例的events選項內接收。

在vue2.x中廢棄了上述兩種方法。在vue2.x中推薦使用一個空的vue實例作為中央事件總線(bus),也就是一個中介。

如下:

<div id="app">
        <p>信息:{{message}}</p>
        <my-component></my-component>
    </div>
    <script type="text/javascript">
        var bus = new Vue();
        Vue.component(‘my-component‘,{
            template:‘<button @click="handleEvent"></button>‘,
            methods:{
                handleEvent:function(event){
                    bus.$emit(‘on-message‘,‘來自組件component的內容‘);
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                message:‘‘
            },
            mounted:function(){
                var _this = this;
                bus.$on(‘on-message‘,function(msg){
                    _this.message=msg;
                 })
            }

        });
    </script>

定義一個空的vue實例當做中間人,在鉤子函數mounted中監聽了來自bus的事件on-message,在組件中會點擊按鈕通過bus把事件on-message發出去,此時app會接收到來自bus的事件。

除了中間介這種方式外,還有兩種方法可實現組件間的通信:父鏈和子組件索引。

父鏈

子組件中使用this.$parent可直接訪問父實例或組件,父組件也可以通過this.$children訪問它所有子組件。

<div id="app">
        <p>信息:{{message}}</p>
        <my-component></my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            template:‘<button @click="handleEvent">通過$parent改變信息內容</button>‘,
            methods:{
                handleEvent:function(event){
                    this.$parent.message=‘子組件通過$parent改變了信息內容‘;
                }
            }
        });
        var app = new Vue({
            el:"#app",
            data:{
                message:‘‘
            }

        });
    </script>

子組件內通過this.$parent.message直接修改信息內容,正常使用不推薦使用。因為這樣使得父子組件緊耦合,只看父組件,很難理解父組件狀態,因為它可能被任意組件修改。最好還是通過props和$emit來通信

子組件索引

<div id="app">
        <button @click="handleRef">通過red獲取子組件實例</button>
        <my-component ref="Mycom"></my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            template:‘<div>子組件</div>‘,
            data:function() {
                return {
                    message:‘子組件內容‘
                }
            }
        });
        var app = new Vue({
            el:"#app",
            methods:{
                handleRef:function(){
                    var msg = this.$refs.Mycom.message;
                    console.log(msg)
                }
            }

        });
    </script>

父組件內的內容通過this.$refs.Mycom找到子組件,並輸出了子組件的內容。

使用slot分發內容

當需要讓組件組合使用,混合父組件發的內容與子組件的模板時,就會用到slot,這個過程叫做內容分發。

slot用法

單個slot: 在子組件標簽內的素有內容替代子組件的<slot>標簽及它的內容。如下:

<div id="app">
        <child-component>
            <p>分發的內容</p>
            <p>更多分發的內容</p>
        </child-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘child-component‘,{
            template:‘<div><slot><p>如果沒有父組件插入內容,我將默認出現</p></slot></div>‘,
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

當父組件沒有插入分發的內容,即上述代碼沒有

<p>分發的內容</p>

<p>跟多分發的內容</p>

的時候,子組件將默認顯示子組件裏slot的內容,如果父組件裏插入了分發的內容,則將替換掉子組件裏slot標簽裏的全部內容。

具名slot

給slot元素指定一個name後可以分發多個內容,具名slot可以與單個slot共存。

如下:

<div id="app">
        <child-component>
            <h2 slot="header">標題</h2>
            <p>正文內容</p>
            <p>更多正文內容</p>
            <div slot="footer">底部信息</div>
        </child-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘child-component‘,{
            template:`<div class="container">
                        <div class="header">
                            <slot name="header"></slot>
                        </div>
                        <div class="main">
                            <slot></slot>
                        </div>
                        <div class="footer">
                            <slot name="footer"></slot>
                        </div>
                      </div>`,
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

在父組件上面可以具名插入slot的name,然後子組件會更具name插入具體的內容。

作用域插槽

是一種特殊的slot使用一個可以復用的模板替換已渲染的元素。

<div id="app">
        <child-component>
            <template scope="props">
                <p>來自父組件的內容</p>
                <p>{{props.msg}}</p>
            </template>
        </child-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘child-component‘,{
            template:`<div class="container">
                        <slot msg="來自子組件的內容"></slot>
                      </div>`,
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

子組件模板,在slot元素上有一個類似pros傳遞數據給組件的寫法 msg="XXX",將數據傳到了插槽。父組件使用了template且擁有一個scope="props"的特性,這裏的props知識一個臨時變量,通過臨時變量props來訪問來自子組件的數據msg。渲染的最終結果為:

<div id="app">
    <div class="container">
        <p>來自父組件的內容</p>
        <p>來自子組件的內容</p>
    </div>
</div>

作用域插槽具代表性的用例是列表組件,允許組件自定義應該如何渲染列表的每一頁。

<div id="app">
        <my-list :book="books">
            <!--作用域插槽也可以是具名的slot-->
            <template slot="book" scope="props">
                <li>{{props.bookName}}</li>
            </template>
        </my-list>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-list‘,{
            props:{
                books:{
                    type:Array,
                    default:function(){
                        return [];
                    }
                }
            },
            template:‘<ul><slot name="book" v-for="book in books" :bookName="book.name"></slot></ul>‘,
        });
        var app = new Vue({
            el:"#app",
            data:{
                books:[
                    {name:‘js‘},
                    {name:‘css‘},
                    {name:‘html‘}
                ]


            }
        });
    </script>

子組件my-list接收一個來自父級的prop數組books,並且將它在name為book的slot上使用v-for指令循環,同時暴露一個變量bookName。

訪問slot

vue2.x提供了用來訪問被slot分發的內容的方法$slots。

<div id="app">
        <my-component>
            <h2 slot="header">標題</h2>
            <p>正文內容</p>
            <div slot="footer">底部信息</div>
        </my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            template:`<div class="container">
                        <div class="header">
                            <slot name="header"></slot>
                        </div>
                        <div class="main">
                            <slot></slot>
                        </div>
                        <div class="footer">
                            <slot name="footer"></slot>
                        </div>
                      </div>`,
            mounted:function(){
                var header = this.$slots.header;
                var main = this.$slots.default;
                var footer = this.$slots.footer;

                console.log(footer[0].elm.innerHTML)
            }
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

在子組件鉤子函數中能用$slots得到對應的slot。

組件的高級用法

遞歸組件

組件可以在它的模板內調用自己,只要給組件設置name屬性就可以。

<div id="app">
        <my-component :count="1"></my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            name:‘my-component‘,
            props:{
                count:{
                    type:Number,
                    default:function(){
                        return 1;
                    }
                }
            },
            template:‘<div class="child"><my-component :count="count+1" v-if="count<3"></my-component></div>‘,


        });
        var app = new Vue({
            el:"#app",
        });
    </script>

設置name後就可以遞歸使用了,不過需要一個限制條件,不然會報錯。

內聯模板

vue提供了一個內斂模板的功能,在使用組件時,給組件標簽使用inline-template特性,組件就會把它的內容當做模板,而不是把它當內容分發。

<div id="app">
        <my-component inline-template>
            <div>
                <h2>父組件定義子組件模板</h2>
                <p>{{message}}</p>
                <p>{{msg}}</p>
            </div>
        </my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            data:function(){
                return {
                    msg:‘子組件內容‘
                }
            }


        });
        var app = new Vue({
            el:"#app",
            data:{
                message:‘父組件內容‘
            }
        });
    </script>

渲染成:

<div>
   <h2>父組件定義子組件模板</h2>
   <p>父組件內容</p>
   <p>子組件內容</p>
</div>

使用內聯模板會導致作用域混淆,應避免使用。

動態組件

vue.js提供了一個特殊的元素<component>用來動態掛載不同的組件,使用is特性來選擇掛載的組件。

<div id="app">
        <component :is="currentView"></component>
        <button @click="handleChangeView(‘A‘)">切換到A</button>
        <button @click="handleChangeView(‘B‘)">切換到B</button>
        <button @click="handleChangeView(‘C‘)">切換到C</button>
    </div>
    <script type="text/javascript">
        var app = new Vue({
            el:"#app",
            components:{
              comA:{
                  template:‘<div>A模板</div>‘
              },
              comB:{
                  template:‘<div>B模板</div>‘
              },
              comC:{
                  template:‘<div>C模板</div>‘
              },
            },
            data:{
                currentView:‘comA‘,
            },
            methods:{
                handleChangeView:function (component){
                    this.currentView=‘com‘+component
                }
            }
        });
    </script>

動態地改變currentView就可以切換視圖。

異步組件

當工程足夠大,使用組件足夠多時,是時候考慮下性能了,因為一開始把所有組件都加載時時一筆開銷。vue.js允許將組件定義為一個工廠函數,動態地解析組件。

<div id="app">
        <my-component></my-component>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,function(resolve,reject){
            setTimeout(()=>{
                resolve({
                    template:‘<div>我是異步渲染的</div>‘
                })
            },2000)
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

$nextTick

當v-if,動態切換顯示時,直接去取div的內容時獲取不到的,因為此時div還沒有被創建出來。這裏涉及到一個vue的重要概念:異步更新隊列。

vue在觀察到數據變化時並不是直接更新DOM,而是開啟一個隊列,並緩沖在同一事件循環中發生的數據變化。

$nextTick就是用來知道DOM什麽時候更新好的。

X-template

沒有使用webpack,gulp等工具,組件內容復雜,如果都在javascript中拼接字符串,效率很低,vue提供了另一種定義模板的方式,在script標簽使用text/template類型,指定一個ID,將這個id賦給template。

<div id="app">
        <my-component></my-component>
        <script type="text/x-template" id="my-component">
            <div>這是組件內容</div>
        </script>
    </div>
    <script type="text/javascript">
        Vue.component(‘my-component‘,{
            template:"#my-component"
        });
        var app = new Vue({
            el:"#app",
        });
    </script>

vue的初衷不是濫用它,因為它將模板和組件的其他定義隔離了。

vue.js 精學組件記錄