Vue 深入理解組件(三)

一.組件使用的細節點

1.瀏覽器解析問題

 <div id="root">
           <table>
               <tbody>
                      <row></row>
                      <row></row>
                      <row></row>
               </tbody>
           </table>
     </div>

     <script>

           Vue.component("row",{
               template:"<tr><td>this is a row</td></tr>"
           })

           var vm = new Vue({
               el:"#root"
           })
     </script>

在這裏插入圖片描述

  • 三個tr沒有在tbody裏面,而tbody要求必須放有tr
  • 可以用is屬性,讓這個tr等價於row
  • 這樣解析就不會有問題
 <div id="root">
           <table>
               <tbody>
                      <tr is="row"></tr>
                      <tr is="row"></tr>
                      <tr is="row"></tr>
               </tbody>
           </table>
     </div>

     <script>

           Vue.component("row",{
               template:"<tr><td>this is a row</td></tr>"
           })

           var vm = new Vue({
               el:"#root"
           })
     </script>

在這裏插入圖片描述

  • ul-li、select-option也是同樣的解決方法

2.組件中的data

  • 在子組件中運用data,頁面報錯
<div id="root">
           <table>
               <tbody>
                  <tr is="row"></tr>
                  <tr is="row"></tr>
                  <tr is="row"></tr>
               </tbody>
           </table>
     </div>

     <script>

           Vue.component("row",{
               data:{
                 content:"this is a row"
               },
               template:"<tr><td>{{content}}</td></tr>"
           })

           var vm = new Vue({
               el:"#root"
           })
     </script>

在這裏插入圖片描述

  • data應該是function 而不是對象
  • 在最外層的vue實例中定義data用對象沒問題
  • 但是在非根組件中,比如子組件定義時只能用function,而不能用對象格式
<div id="root">
           <table>
               <tbody>
                  <tr is="row"></tr>
                  <tr is="row"></tr>
                  <tr is="row"></tr>
               </tbody>
           </table>
     </div>

     <script>

           Vue.component("row",{
               data:function(){
                   return {
                       content:"this is a row"
                   }
               },
               template:"<tr><td>{{content}}</td></tr>"
           })

           var vm = new Vue({
               el:"#root"
           })
     </script>
  • 子組件定義data,data只能是函數,而不能是對象
  • 一個子組件不想根組件只調用一次,它可能在不同的地方,比如row子組件調用三次
  • 每個子組件的數據不希望和其他的子組件產生衝突(每個子組件應該有自己的數據,比如上面的row是三套數據,而不是共享一個數據)
  • 通過一個函數返回一個對象的目的,讓子組件擁有獨立的數據存儲,不會出現多個子組件相互影響的情況

3.vue中的ref引用

  • vue不建議我們在代碼中操作dom
  • 但是複雜的動畫效果,光靠vue數據綁定是不行的,所以有些必要情況下必須要操作dom
  • vue通過ref引用形式操作dom
<div id="root">
    <div ref="hello" @click="handleClick">hello world</div>
</div>

<script>

    Vue.component("row",{
        data:function(){
            return {
                content:"this is a row"
            }
        },
        template:"<tr><td>{{content}}</td></tr>"
    })

    var vm = new Vue({
        el:"#root",
        methods:{
            handleClick:function () {
                alert(this.$refs.hello.innerHTML)
            }
        }
    })
</script>
  • vue所有的引用下的名爲hello的引用,對應的就是這個div的dom,指向的就是這個div的dom節點
  • 如果一個組件上加了ref,this.$refs.xxx就是對應這個組件的引用
  • 實現一個點擊第一、二行都自增1,第三行顯示第一行+第二行的和,這裏就要用到ref引用
    在這裏插入圖片描述
 <div id="root">
            <counter ref="one" @change="handleChange"></counter>
            <counter ref="two" @change="handleChange"></counter>
            <div>{{total}}</div>
    </div>

    <script>
    
        Vue.component("counter",{
            template:"<div @click='handleClick'>{{number}}</div>",
            data:function(){
                return{
                    number:0
                }
            },
            methods:{
                handleClick:function () {
                     this.number++;
                     this.$emit("change")
                }
            }
        })
    
        var vm = new Vue({
            el:"#root",
            data:{
                total:0
            },
            methods:{
                handleChange:function () {
                     this.total = this.$refs.one.number + this.$refs.two.number
                }
            }
        })
    </script>

二.父子組件的間的數據傳遞

1.父組件向子組件數據傳遞

  • 此時count中的0和1都只是字符串
<div id="root">
    <counter count="0"></counter>
    <counter count="1"></counter>
</div>

<script>

    var counter = {
        template: "<div>0</div>"
    }

    var vm = new Vue({
        el:"#root",
        components:{
            counter:counter
        }
    })
</script>
  • 加了冒號就是js表達式,也就是數字類型
<div id="root">
    <counter :count="0"></counter>
    <counter :count="1"></counter>
</div>

<script>

    var counter = {
        template: "<div>0</div>"
    }

    var vm = new Vue({
        el:"#root",
        components:{
            counter:counter
        }
    })
</script>
  • 子組件要接收這個數據才能使用,所以用props屬性來接收
<div id="root">
    <counter :count="0"></counter>
    <counter :count="1"></counter>
</div>

<script>

    var counter = {
        props:['count'],
        template: "<div>{{count}}</div>"
    }

    var vm = new Vue({
        el:"#root",
        components:{
            counter:counter
        }
    })
</script>
  • 子組件累加的功能
<div id="root">
    <counter :count="0"></counter>
    <counter :count="1"></counter>
</div>

<script>

    var counter = {
        props:['count'],
        template: "<div @click='handleAdd'>{{count}}</div>",
        methods:{
            handleAdd:function () {
                 this.count++
            }
        }
    }

    var vm = new Vue({
        el:"#root",
        components:{
            counter:counter
        }
    })
</script>

雖然可以實現,但是還是報了警告
在這裏插入圖片描述

  • 不能直接修改父組件傳遞過來的數據,vue有個單向數據流的概念,一旦接收的count不是基礎類型數據,如果是對象形式,引用類型時,子組件改變了傳遞過來的內容,有可能接收的引用的數據還在被其他子組件所使用,這樣子組件不僅影響了自己,還影響了其他子組件。所以不能改變父組件傳遞過來的數據
  • 我們可以拷貝一份來實現這個累加功能
<div id="root">
    <counter :count="0"></counter>
    <counter :count="1"></counter>
</div>

<script>

    var counter = {
        props:['count'],
        data:function(){
            return{
               number:this.count
            }
        },
        template: "<div @click='handleAdd'>{{number}}</div>",
        methods:{
            handleAdd:function () {
                 this.number++
            }
        }
    }

    var vm = new Vue({
        el:"#root",
        components:{
            counter:counter
        }
    })
</script>

2.子組件向父組件數據傳遞

  • 簡單說就是讓子組件向外觸發事件
  • 父組件監聽這個事件
<div id="root">
    <counter :count="2" @change="handleIncrease"></counter>
    <counter :count="3" @change="handleIncrease"></counter>
    <div>{{total}}</div>
</div>

<script>

    var counter = {
        props:['count'],
        data:function(){
            return{
               number:this.count
            }
        },
        template: "<div @click='handleAdd'>{{number}}</div>",
        methods:{
            handleAdd:function () {
                 this.number = this.number + 2
                 this.$emit("change",2)
            }
        }
    }

    var vm = new Vue({
        el:"#root",
        components:{
            counter:counter
        },
        data:{
            total:5
        },
        methods:{
            handleIncrease:function (msg) {
                 this.total = this.total + msg
            }
        }
    })
</script>

三.組件參數校驗與非props特性

1.組件參數校驗

  • 平常情況下都這麼寫的
<div id="root">
     <counter count="123"></counter>
     <counter count="321"></counter>
</div>

<script>

    Vue.component("counter",{
        props:["count"],
        template:"<div>{{count}}</div>"
    })

    var vm = new Vue({
        el:"#root"
    })
</script>
  • 如果你要加參數校驗,就要把props從數組改成對象
  • 要求count爲字符串(加了冒號123就變成數字了,所以報錯)
<div id="root">
     <counter count="hello"></counter>
     <counter :count="123"></counter>
</div>

<script>

    Vue.component("counter",{
        props:{
            count:String
        },
        template:"<div>{{count}}</div>"
    })

    var vm = new Vue({
        el:"#root"
    })
</script>

在這裏插入圖片描述

  • 可以設置成多種類型
<div id="root">
     <counter count="hello"></counter>
     <counter :count="123"></counter>
</div>

<script>

    Vue.component("counter",{
        props:{
            count:[String,Number]
        },
        template:"<div>{{count}}</div>"
    })

    var vm = new Vue({
        el:"#root"
    })
</script>
  • 此時傳對象就會報錯
<div id="root">
     <counter :count="{a:1}"></counter>
</div>
  • 數據後邊也可以跟着對象(類型、是否必要、默認值)
<div id="root">
     <counter></counter>
</div>

<script>

    Vue.component("counter",{
        props:{
            count:{
                type:String,
                required:false,
                default:'哈哈'
            }
        },
        template:"<div>{{count}}</div>"
    })

    var vm = new Vue({
        el:"#root"
    })
</script>
  • 以及要求長度之類的validator對象(校驗器)
<div id="root">
     <counter count="hello world"></counter>
</div>

<script>

    Vue.component("counter",{
        props:{
            count:{
                type:String,
                validator:function (value) {
                    return (value.length > 5)
                }
            }
        },
        template:"<div>{{count}}</div>"
    })

    var vm = new Vue({
        el:"#root"
    })
</script>

2.props特性

  • 父組件使用子組件時,通過屬性子組件傳值時,子組件申明瞭對父組件傳遞過來的數據的接收
  • 比如父組件傳遞content 給子組件,子組件恰好又申明瞭content
  • 屬性傳遞不會在dom節點上顯示的,子組件也能把傳遞數據通過插值表達式顯示出來
<div id="root">
    <child content="hello world"></child>
</div>

<script>

    Vue.component("child",{
        props:{
            content:{
                type:String
            }
        },
        template:"<div>{{content}}</div>"
    })

    var vm = new Vue({
        el:"#root"
    })
</script>

3.非props特性

  • 父組件傳遞數據,子組件並沒有申明接收父組件傳遞的內容
<div id="root">
    <child content="hello world"></child>
</div>

<script>

    Vue.component("child",{
        template:"<div>hello</div>"
    })

    var vm = new Vue({
        el:"#root"
    })
</script>
  • 雖然會報錯,但是網頁會顯示dom屬性
  • 實際情況下非props特性很少用到
    在這裏插入圖片描述

四.給組件綁定原生事件

  • 定義一個全局的子組件,按邏輯實例中methods寫這個方法,但是會發現沒有用
    <div id="root">
        <child @click="handleClick"></child>
    </div>

<script>

    Vue.component("child",{
        template:"<div>Child</div>",
        methods:{
            handleClick:function () {
                alert('1')
            }
        }
    })

    var vm = new Vue({
        el:"#root"
    })
</script>
  • 當給組件綁定事件的時候,這個事件綁定的是自定義的事件
  • 真正鼠標點擊觸發的事件並不是我綁定的這個click事件
  • 在組件中給元素綁定事件,這個纔是原生事件,而Child綁定的click是自定義事件
  • 父組件這裏自定義事件監聽用this.$emit
  • 點擊時,子組件會監聽到div元素被點擊了,然後執行這個自定義的事件,向外觸發一個自定義click事件,父組件用子組件監聽了這個事件,所以對應代碼會執行 ,但是這樣寫代碼很麻煩
    <div id="root">
        <child @btn="handleClick"></child>
    </div>

<script>

    Vue.component("child",{
        template:"<div @click='handleChildClick'>Child</div>",
        methods:{
            handleChildClick:function () {
                alert('handleChildClick')
                this.$emit("btn")
            }
        }
    })

    var vm = new Vue({
        el:"#root",
        methods:{
            handleClick:function(){
                alert('handleClick')
            }
        }
    })
</script>
  • 如果非要child監聽原生的事件,可以加上.native修飾符就可以

    <div id="root">
        <child @click.native="handleChildClick"></child>
    </div>

<script>

    Vue.component("child",{
        template:"<div>Child</div>",
    })

    var vm = new Vue({
        el:"#root",
        methods:{
            handleChildClick:function () {
                alert('handleChildClick')
            }
        }
    })
</script>

五.非父子組件間的傳值

1.非父子組件間的傳值介紹

在這裏插入圖片描述

  • 假如我們拆分成3個組件
  • 第一層和第二層傳遞數據就是父子組件傳值
    • 父組件通過props向子組件傳值
    • 子組件通過向外觸發事件向父組件傳值
  • 那麼萬一是第一層和第三層傳遞數據呢?
    • 不能通過屬性直接傳遞數據
    • 把第一層數據傳給第二層然後在傳給第三層…反過來也一樣,但是這樣就複雜了
      在這裏插入圖片描述
  • 萬一是左邊第3層像右邊第3層傳值呢?難道還一層一層傳遞?
  • Vue是輕量級視圖框架,不是把事情變複雜的
  • 如果遇到複雜的數據傳遞,光靠vue框架是解決不了問題的,要引入一些設計模式之類的
  • 一般有兩種解決方式(解決複雜的非父子組件傳值)
    • 藉助vue官方的數據層框架vuex(使用起來有難度)—後邊結合項目來說
    • 使用bus格式來解決(vue稱爲總線機制/發佈訂閱模式/觀察者模式)

2.總線機制

  • 我們通過組件間傳值例子來講解
  • 在vue實例掛載一個bus屬性,bus屬性又指向vue的實例
  • this.bus指的就是實例上掛載的bus,每個實例上都會有bus這個屬性,而bus又是一個vue實例,所以它下面就會有$emit方法
<div id="root">
    <child content="hello"></child>
    <child content="world"></child>
</div>

<script>

    Vue.prototype.bus = new Vue()

    Vue.component("child",{
        props:{
            content:{
                type:String
            }
        }
        template:"<div @click='handleClick'>{{content}}</div>",
        methods:{
            handleClick:function () {
                this.bus.$emit('change',this.content)
            }
        }
    })

    var vm = new Vue({
        el:"#root"
    })
</script>
  • 接下來就讓其他組件監聽這個事件,可以藉助生命週期鉤子mounted(組件被掛載後執行的函數),$on監聽bus觸發的事件
<div id="root">
    <child content="hello"></child>
    <child content="world"></child>
</div>

<script>

    Vue.prototype.bus = new Vue()

    Vue.component("child",{
        props:{
            content:{
                type:String
            }
        }
        template:"<div @click='handleClick'>{{content}}</div>",
        methods:{
            handleClick:function () {
                this.bus.$emit('change',this.content)
            }
        },
        mounted:function(){
            var this_ = this
            this.bus.$on('change',function (msg) {
                  this_.content = msg //注意作用域
            })
        }
    })

    var vm = new Vue({
        el:"#root"
    })
</script>
  • 這樣兩個組件傳值通過總線模式就搞定了,但是沒有問題了嗎?
  • 雖然實現,但是看瀏覽器還是會報警告(因爲content是父組件傳來的數據,在vue中要時刻有單項數據流的概念)
  • 可以用子組件拷貝父組件傳來的數據的方法
<div id="root">
    <child content="hello"></child>
    <child content="world"></child>
</div>

<script>

    Vue.prototype.bus = new Vue()

    Vue.component("child",{
        props:{
            content:{
                type:String
            }
        },
        data:function(){
          return {
              selfContent : this.content
          }
        },
        template:"<div @click='handleClick'>{{selfContent}}</div>",
        methods:{
            handleClick:function () {
                this.bus.$emit('change',this.selfContent)
            }
        },
        mounted:function(){
            var this_ = this
            this.bus.$on('change',function (msg) {
                  this_.selfContent = msg //注意作用域
            })
        }
    })

    var vm = new Vue({
        el:"#root"
    })
</script>

六.在vue中使用插槽

1.插槽的使用場景

  • 子組件除了展示p標籤外,還要展示一段內容,而這段內容不是子組件決定的,是父組件傳遞過來的
  • 以前的想法怎麼傳?可以通過屬性形式向子組件傳值
  • 但是你會發現做了轉義
<div id="root">
      <child content='<p>world</p>'></child>
</div>

<script>
    Vue.component('child',{
        props:["content"],
        template:'<div><p>hello</p>  {{content}}</div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>
  • 所以只能用模板佔位符
<div id="root">
      <child content='<p>world</p>'></child>
</div>

<script>
    Vue.component('child',{
        props:["content"],
        template:'<div><p>hello</p>  <div v-html="this.content"></div></div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>
  • 這種形式傳遞少可以,但傳遞一旦多了,顯示沒問題,但是代碼很難閱讀,插槽就是解決這個問題出來的

2.插槽的使用

  • 顯示的就是父組件向子組件插入進來的這個p標籤
<div id="root">
      <child>
          <h1>world</h1>
      </child>
</div>

<script>
    Vue.component('child',{
        template:'<div> <p>hello</p> <slot></slot> </div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>

在這裏插入圖片描述

  • 也方便向子組件傳遞dom元素,同時子組件使用插槽內容也很簡單
  • 也可以在slot標籤內寫默認內容

3.插槽的命名

  • 如果要實現header、content、footer用如下代碼是不行的
  • 子組件全部都是插槽的內容,所以就會出現如下下圖的樣子,相當於調用了兩次
  • 此時要給插槽命名
<div id="root">
        <body-content>
                 <div class="header">header</div>
                 <div class="footer">footer</div>
        </body-content>
</div>

<script>
    Vue.component('body-content',{
        template:'<div> <slot></slot>  <div class="content">content</div>  <slot></slot></div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>

在這裏插入圖片描述

<div id="root">
        <body-content>
                 <div class="header" slot="header">header</div>
                 <div class="footer" slot="footer">footer</div>
        </body-content>
</div>

<script>
    Vue.component('body-content',{
        template:'<div> <slot name="header"></slot>  <div class="content">content</div>  <slot name="footer"></slot></div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>

在這裏插入圖片描述

  • 當然也可以設置默認值(當沒有同名的插槽)
<div id="root">
        <body-content>

                 <div class="footer" slot="footer">footer</div>
        </body-content>
</div>

<script>
    Vue.component('body-content',{
        template:'<div> <slot name="header"><h1>default slot</h1></slot>  <div class="content">content</div>  <slot name="footer"></slot></div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>

在這裏插入圖片描述

七.作用域插槽

1.作用域插槽的使用

  • 循環遍歷列表
<div id="root">
        <child></child>
</div>

<script>
    Vue.component('child',{
        data:function(){
            return {
                list:[1,2,3,4]
            }
        },
        template:'<div><ul>   <li v-for="item of list">{{item}}</li>        </ul></div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>
  • 這個child組件會在很多地方調用,列表樣式應該不是由child控制的,而是由外部決定的
<div id="root">
        <child>
             <template slot-scope="props">
                 <h1> {{props.item}} -- hello </h1>
             </template>
        </child>
</div>

<script>
    Vue.component('child',{
        data:function(){
            return {
                list:[1,2,3,4]
            }
        },
        template:'<div><ul>   <slot v-for="item of list" :item=item></slot>        </ul></div>'
    })


    var vm = new Vue({
        el:"#root"
    })
</script>
  • 當子組件用slot時,會往slot傳遞一個item數據,item數據放在slot-scope的屬性之中
  • 這就不是由子組件決定的了,而是父組件調用子組件的時候給子組件去傳遞對應的模板,這就是作用域槽的使用
  • 父組件調用子組件時,給子組件傳了一個插槽,這個插槽是作用域插槽,作用域插槽必須是template的,申明從子組件接收的數據slot-scope放在那裏,告訴子組件模板信息
  • 當子組件做循環或者某一個部分dom結構由外部傳遞進來的時候用作用域插槽,子組件可以像父組件插槽傳遞數據
    ,父組件插槽向接收這個數據,必須外層申明一個template的,接收傳遞數據的放在那裏,傳遞一個item過來 ,接收到這個item

八.動態組件與 v-once 指令

1.v-once指令

  • 實現一個動態互相展示的效果
  • 不過這種方式都是創建一次 銷燬一次(重新),很消耗性能
<div id="root">
        <child-one v-if="type ==='child-one' "></child-one>
        <child-two v-if="type ==='child-two' "></child-two>
        <button @click="handleClick">交換</button>
</div>

<script>
    Vue.component('child-one',{
        template:'<div>child-one</div>'
    })

    Vue.component('child-two',{
        template:'<div>child-two</div>'
    })

    var vm = new Vue({
        el:"#root",
        data:{
            type:'child-one'
        },
        methods:{
            handleClick:function () {
                 this.type = (this.type === 'child-one'? 'child-two':'child-one')
            }
        }
    })
</script>
  • 可以用v-once指令優化
  • 第一次展示後都會放到內存裏面,切換不會重新創建組件,直接去內存中拿的,性能更高
<div id="root">
    <child-one v-if="type ==='child-one' "></child-one>
    <child-two v-if="type ==='child-two' "></child-two>
        <button @click="handleClick">交換</button>
</div>

<script>
    Vue.component('child-one',{
        template:'<div v-once>child-one</div>'
    })

    Vue.component('child-two',{
        template:'<div v-once>child-two</div>'
    })

    var vm = new Vue({
        el:"#root",
        data:{
            type:'child-one'
        },
        methods:{
            handleClick:function () {
                 this.type = (this.type === 'child-one'? 'child-two':'child-one')
            }
        }
    })
</script>

2.動態組件

  • 也可以用動態組件的形式來展示這個效果
  • component是vue自帶的,會根據type屬性自帶的變化加載不同的組件
<div id="root">
        <component :is="type"></component>
        <button @click="handleClick">交換</button>
</div>

<script>
    Vue.component('child-one',{
        template:'<div>child-one</div>'
    })

    Vue.component('child-two',{
        template:'<div>child-two</div>'
    })

    var vm = new Vue({
        el:"#root",
        data:{
            type:'child-one'
        },
        methods:{
            handleClick:function () {
                 this.type = (this.type === 'child-one'? 'child-two':'child-one')
            }
        }
    })
</script>
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章