1. 程式人生 > 實用技巧 >Vue筆記 - [元件]

Vue筆記 - [元件]

元件

  • 元件 (Component) 是 Vue.js 最強大的功能之一
  • 元件可以擴充套件 HTML 元素,封裝可重用的代

元件註冊

全域性註冊

  • Vue.component('元件名稱', { }) 第1個引數是標籤名稱,第2個引數是一個選項物件
  • 全域性元件註冊後,任何vue例項都可以用
元件基礎用
<div id="example">
  <!-- 2、 元件使用 元件名稱 是以HTML標籤的形式使用  -->  
  <my-component></my-component>
</div>
<script>
    //   註冊元件 
    // 1、 my-component 就是元件中自定義的標籤名
	Vue.component('my-component', {
      template: '<div>A custom component!</div>'
    })

    // 建立根例項
    new Vue({
      el: '#example'
    })

</script>
元件注意事項
  • 元件引數的data值必須是函式同時這個函式要求返回一個物件
  • 元件模板必須是單個根元素
  • 元件模板的內容可以是模板字串
  <div id="app">
     <!-- 
		4、  元件可以重複使用多次 
	      因為data中返回的是一個物件所以每個元件中的資料是私有的
		  即每個例項可以維護一份被返回物件的獨立的拷貝   
	--> 
    <button-counter></button-counter>
    <button-counter></button-counter>
    <button-counter></button-counter>
      <!-- 8、必須使用短橫線的方式使用元件 -->
     <hello-world></hello-world>
  </div>

<script type="text/javascript">
	//5  如果使用駝峰式命名元件,那麼在使用元件的時候,只能在字串模板中用駝峰的方式使用元件,
    // 7、但是在普通的標籤模板中,必須使用短橫線的方式使用元件
     Vue.component('HelloWorld', {
      data: function(){
        return {
          msg: 'HelloWorld'
        }
      },
      template: '<div>{{msg}}</div>'
    });
    
    
    
    Vue.component('button-counter', {
      // 1、元件引數的data值必須是函式 
      // 同時這個函式要求返回一個物件  
      data: function(){
        return {
          count: 0
        }
      },
      //  2、元件模板必須是單個根元素
      //  3、元件模板的內容可以是模板字串  
      template: `
        <div>
          <button @click="handle">點選了{{count}}次</button>
          <button>測試123</button>
			#  6 在字串模板中可以使用駝峰的方式使用元件	
		   <HelloWorld></HelloWorld>
        </div>
      `,
      methods: {
        handle: function(){
          this.count += 2;
        }
      }
    })
    var vm = new Vue({
      el: '#app',
      data: {}
    });
  </script>

區域性註冊

  • 只能在當前註冊它的vue例項中使用
  <div id="app">
      <my-component></my-component>
  </div>

<script>
    // 定義元件的模板
    var Child = {
      template: '<div>A custom component!</div>'
    }
    new Vue({
      //區域性註冊元件  
      components: {
        // <my-component> 將只在父模板可用  一定要在例項上註冊了才能在html檔案中使用
        'my-component': Child
      }
    })
 </script>

組建的命名

  • 短橫線方式

Vue.component('my-component',{/* ... */})

  • 駝峰方式(只能在字串模板中使用該種方式,普通標籤模板中使用短橫線方式

Vue.component('MyComponent',{/* ... */})

Vue除錯工具

Vue-Devtools --> Chrome外掛

Vue元件之間傳值

父元件向子元件傳值

  • 父元件傳送的形式是以屬性的形

  • 然後子元件用屬性props接收

  • 在props中使用駝峰形式,模板中需要使用短橫線的形式字串形式的模板中沒有這個限制

  <div id="app">
    <div>{{pmsg}}</div>
     <!--1、menu-item  在 APP中巢狀著 故 menu-item   為  子元件      -->
     <!-- 給子元件傳入一個靜態的值 -->
    <menu-item title='來自父元件的值'></menu-item>
    <!-- 2、 需要動態的資料的時候 需要屬性繫結的形式設定 此時 ptitle  來自父元件data 中的資料 . 
		  傳的值可以是數字、物件、陣列等等
	-->
    <menu-item :title='ptitle' content='hello' menu-title='html中用短橫線'></menu-item>
  </div>

  <script type="text/javascript">
    Vue.component('menu-item', {
      // 3、 子元件用屬性props接收父元件傳遞過來的資料  
      // Javascript中命名使用駝峰命名,html中用短橫線,字串模板中沒有限制
      props: ['title', 'content','menuTitle'],
      data: function() {
        return {
          msg: '子元件本身的資料'
        }
      },
      template: '<div>{{msg + "----" + title + "-----" + content + " ---- " + menuTitle}}</div>'
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父元件中內容',
        ptitle: '動態繫結屬性'
      }
    });
  </script>

子元件向父元件傳值

  • 子元件用$emit()觸發事件
  • $emit() 第一個引數為 自定義的事件名稱 第二個引數為需要傳遞的資料
  • 父元件用v-on 監聽子元件的事件
 <div id="app">
    <div :style='{fontSize: fontSize + "px"}'>{{pmsg}}</div>
     <!-- 2 父元件用v-on 監聽子元件的事件
		這裡 enlarge-text  是從 $emit 中的第一個引數對應   handle 為對應的事件處理函式	
	-->	
    <menu-item :parr='parr' @enlarge-text='handle($event)'></menu-item>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      子元件向父元件傳值-攜帶引數
    */
    
    Vue.component('menu-item', {
      props: ['parr'],
      template: `
        <div>
          <ul>
            <li :key='index' v-for='(item,index) in parr'>{{item}}</li>
          </ul>
			###  1、子元件用$emit()觸發事件
			### 第一個引數為 自定義的事件名稱   第二個引數為需要傳遞的資料  
          <button @click='$emit("enlarge-text", 5)'>擴大父元件中字型大小</button>
          <button @click='$emit("enlarge-text", 10)'>擴大父元件中字型大小</button>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        pmsg: '父元件中內容',
        parr: ['apple','orange','banana'],
        fontSize: 10
      },
      methods: {
        handle: function(val){
          // 擴大字型大小
          this.fontSize += val;
        }
      }
    });
  </script>

兄弟之間的傳遞

  • 兄弟之間傳遞資料需要藉助於事件中心,通過事件中心傳遞資料
    • 提供事件中心 var hub = new Vue()
  • 傳遞資料方,通過一個事件觸發hub.$emit(方法名,傳遞的資料)
  • 接收資料方,通過mounted(){} 鉤子中 觸發hub.$on()方法名
  • 銷燬事件 通過hub.$off()方法名銷燬之後無法進行傳遞資料
 <div id="app">
    <div>父元件</div>
    <div>
      <button @click='handle'>銷燬事件</button>
    </div>
    <test-tom></test-tom>
    <test-jerry></test-jerry>
  </div>
  <script type="text/javascript" src="js/vue.js"></script>
  <script type="text/javascript">
    /*
      兄弟元件之間資料傳遞
    */
    //1、 提供事件中心
    var hub = new Vue();

    Vue.component('test-tom', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>TOM:{{num}}</div>
          <div>
            <button @click='handle'>點選</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、傳遞資料方,通過一個事件觸發hub.$emit(方法名,傳遞的資料)   觸發兄弟元件的事件
          hub.$emit('jerry-event', 2);
        }
      },
      mounted: function() {
       // 3、接收資料方,通過mounted(){} 鉤子中  觸發hub.$on(方法名
        hub.$on('tom-event', (val) => {
          this.num += val;
        });
      }
    });
    Vue.component('test-jerry', {
      data: function(){
        return {
          num: 0
        }
      },
      template: `
        <div>
          <div>JERRY:{{num}}</div>
          <div>
            <button @click='handle'>點選</button>
          </div>
        </div>
      `,
      methods: {
        handle: function(){
          //2、傳遞資料方,通過一個事件觸發hub.$emit(方法名,傳遞的資料)   觸發兄弟元件的事件
          hub.$emit('tom-event', 1);
        }
      },
      mounted: function() {
        // 3、接收資料方,通過mounted(){} 鉤子中  觸發hub.$on()方法名
        hub.$on('jerry-event', (val) => {
          this.num += val;
        });
      }
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      },
      methods: {
        handle: function(){
          //4、銷燬事件 通過hub.$off()方法名銷燬之後無法進行傳遞資料  
          hub.$off('tom-event');
          hub.$off('jerry-event');
        }
      }
    });
  </script>

元件插槽

  • 元件的最大特性就是複用性,而用好插槽能大大提高元件的可複用能力

匿名插槽

<div id="app">
    <!-- 這裡的所有元件標籤中巢狀的內容會替換掉slot  如果不傳值 則使用 slot 中的預設值  -->  
    <alert-box>有bug發生</alert-box>
    <alert-box>有一個警告</alert-box>
    <alert-box></alert-box>
  </div>

  <script type="text/javascript">
    /*
      元件插槽:父元件向子元件傳遞內容
    */
    Vue.component('alert-box', {
      template: `
        <div>
          <strong>ERROR:</strong>
		# 當元件渲染的時候,這個 <slot> 元素將會被替換為“元件標籤中巢狀的內容”。
		# 插槽內可以包含任何模板程式碼,包括 HTML
          <slot>預設內容</slot>
        </div>
      `
    });
    var vm = new Vue({
      el: '#app',
      data: {
        
      }
    });
  </script>
</body>
</html>

具名插槽

  • 具有名字的插槽
  • 使用 中的 "name" 屬性繫結元素
<div id="app">
    <base-layout>
        <!-- 2、 通過slot屬性來指定, 這個slot的值必須和下面slot元件得name值對應上
如果沒有匹配到 則放到匿名的插槽中   --> 
        <p slot='header'>標題資訊</p>
        <p>主要內容1</p>
        <p>主要內容2</p>
        <p slot='footer'>底部資訊資訊</p>
    </base-layout>

    <base-layout>
        <!-- 注意點:template臨時的包裹標籤最終不會渲染到頁面上     -->  
        <template slot='header'>
            <p>標題資訊1</p>
            <p>標題資訊2</p>
        </template>
        <p>主要內容1</p>
        <p>主要內容2</p>
        <template slot='footer'>
            <p>底部資訊資訊1</p>
            <p>底部資訊資訊2</p>
        </template>
    </base-layout>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
    /*
      具名插槽
    */
    Vue.component('base-layout', {
        template: `
<div>
<header>
###	1、 使用 <slot> 中的 "name" 屬性繫結元素 指定當前插槽的名字
<slot name='header'></slot>
    </header>
<main>
<slot></slot>
    </main>
<footer>
###  注意點: 
###  具名插槽的渲染順序,完全取決於模板,而不是取決於父元件中元素的順序
<slot name='footer'></slot>
    </footer>
    </div>
`
    });
    var vm = new Vue({
        el: '#app',
        data: {

        }
    });
</script>
</body>
</html>

作用域插槽

  • 父元件對子元件加工處理
  • 既可以複用子元件的slot,又可以使slot內容不一致
<div id="app">
    <!-- 
1、當我們希望li 的樣式由外部使用元件的地方定義,因為可能有多種地方要使用該元件,
但樣式希望不一樣 這個時候我們需要使用作用域插槽 

-->  
    <fruit-list :list='list'>
        <!-- 2、 父元件中使用了<template>元素,而且包含scope="slotProps",
slotProps在這裡只是臨時變數   
---> 	
        <template slot-scope='slotProps'>
            <strong v-if='slotProps.info.id==3' class="current">
                {{slotProps.info.name}}		         
            </strong>
            <span v-else>{{slotProps.info.name}}</span>
        </template>
    </fruit-list>
</div>
<script type="text/javascript" src="js/vue.js"></script>
<script type="text/javascript">
    /*
      作用域插槽
    */
    Vue.component('fruit-list', {
        props: ['list'],
        template: `
<div>
<li :key='item.id' v-for='item in list'>
###  3、 在子元件模板中,<slot>元素上有一個類似props傳遞資料給元件的寫法msg="xxx",
###   插槽可以提供一個預設內容,如果如果父元件沒有為這個插槽提供了內容,會顯示預設的內容。
如果父元件為這個插槽提供了內容,則預設的內容會被替換掉
<slot :info='item'>{{item.name}}</slot>
    </li>
    </div>
`
    });
    var vm = new Vue({
        el: '#app',
        data: {
            list: [{
                id: 1,
                name: 'apple'
            },{
                id: 2,
                name: 'orange'
            },{
                id: 3,
                name: 'banana'
            }]
        }
    });
</script>
</body>
</html>

v-slot

v-slot的出現是為了代替原有的slot和slot-scope,簡化了一些複雜的語法。
一句話概括就是v-slot 後邊是插槽名稱,= 後邊是元件內部繫結作用域值的對映。

<div id="app">
    <h1>{{msg}}</h1>
    <fruits-com :list="list" >
        <template v-slot:fruit="item">
            <strong v-if="item.info.id === currentId" class="current">{{item.info.name}}</strong>
            <span v-else>{{item.info.name}}</span>
        </template>
    </fruits-com>
</div>

<script>
    Vue.component("fruits-com",{
        props:['list'],
        template:`
            <div>
                <li :key="item.id" v-for="item in list">
                    <slot name="fruit" :info="item">{{item.name}}</slot>
                </li>
            </div>
        `
    })
    let app = new Vue({
        el: '#app',
        data: {
            msg: 'Hello Vue!',
            currentId: 2,
            list:[
                {
                    id:1,
                    name:'apple'
                },
                {
                    id:2,
                    name:'lemon'
                },
                {
                    id:3,
                    name:'banana'
                }

            ]
        }
    })
</script>
<style scope>
    .current{
        color: orange;
    }
</style>

小案例(購物車)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport"
          content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
    <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script>
    <style>
        .container {
        }

        .container .cart {
            width: 300px;
            margin: auto;
        }

        .container .title {
            background-color: lightblue;
            height: 40px;
            line-height: 40px;
            text-align: center;
            /*color: #fff;*/
        }

        .container .total {
            background-color: #FFCE46;
            height: 50px;
            line-height: 50px;
            text-align: right;
        }

        .container .total button {
            margin: 0 10px;
            background-color: #DC4C40;
            height: 35px;
            width: 80px;
            border: 0;
        }

        .container .total span {
            color: red;
            font-weight: bold;
        }

        .container .item {
            height: 55px;
            line-height: 55px;
            position: relative;
            border-top: 1px solid #ADD8E6;
        }

        .container .item img {
            width: 45px;
            height: 45px;
            margin: 5px;
        }

        .container .item .name {
            position: absolute;
            width: 90px;
            top: 0;
            left: 55px;
            font-size: 16px;
        }

        .container .item .change {
            width: 100px;
            position: absolute;
            top: 0;
            right: 50px;
        }

        .container .item .change a {
            font-size: 20px;
            width: 30px;
            text-decoration: none;
            background-color: lightgray;
            vertical-align: middle;
        }

        .container .item .change .num {
            width: 40px;
            height: 25px;
        }

        .container .item .del {
            position: absolute;
            top: 0;
            right: 0px;
            width: 40px;
            text-align: center;
            font-size: 40px;
            cursor: pointer;
            color: red;
        }

        .container .item .del:hover {
            background-color: orange;
        }
    </style>
</head>
<body>
<div id="app">
    <div class="container">
        <my-cart></my-cart>
    </div>
</div>

<script>
    let CartTitle = {
        props: ['uname'],
        template: `
            <div class="title">{{uname}}的商品</div>
        `
    }
    let CartList = {
        props: ['list'],
        template: `
            <div>
                <div class="item" :key="item.id" v-for="item in list">
                    <img :src="item.img"/>
                    <div class="name">{{item.name}}</div>
                    <div class="change">
                        <a href="" @click.prevent="sub(item.id)">-</a>
                        <input type="text" class="num" :value="item.num" @blur="changeNum(item.id,$event)"/>
                        <a href="" @click.prevent="add(item.id)">+</a>
                    </div>
                    <div class="del" @click="del(item.id)">×</div>
                </div>
            </div>
        `,
        methods: {
            del: function (id) {
                this.$emit("cart-del", id);
            },
            changeNum: function (id, event) {
                this.$emit("change-num", {
                    id: id,
                    num: event.target.value,
                    type: 'change'
                })
            },
            sub: function (id) {
                this.$emit("change-num", {
                    id: id,
                    type: 'sub'
                })
            },
            add: function (id) {
                this.$emit("change-num", {
                    id: id,
                    type: 'add'
                })
            }
        }
    }
    let CartTotal = {
        props: ['list'],
        template: `
            <div class="total">
                <span>總價:{{total}}</span>
                <button>結算</button>
            </div>
        `,
        computed: {
            total: function () {
                let sum = 0;
                this.list.forEach(item => {
                    sum += item.price * item.num;
                });
                return sum;
            }
        }
    }
    Vue.component('my-cart', {
        data: function () {
            return {
                uname: '張三',
                list: [{
                    id: 1,
                    name: 'TCL彩電',
                    price: 1000,
                    num: 1,
                    img: 'img/a.jpg'
                }, {
                    id: 2,
                    name: '機頂盒',
                    price: 1000,
                    num: 1,
                    img: 'img/b.jpg'
                }, {
                    id: 3,
                    name: '海爾冰箱',
                    price: 1000,
                    num: 1,
                    img: 'img/c.jpg'
                }, {
                    id: 4,
                    name: '小米手機',
                    price: 1000,
                    num: 1,
                    img: 'img/d.jpg'
                }, {
                    id: 5,
                    name: 'PPTV電視',
                    price: 1000,
                    num: 2,
                    img: 'img/e.jpg'
                }]
            }
        },
        template: `
            <div class="cart">
                <cart-title :uname="uname"></cart-title>
                <cart-list :list="list" @cart-del="delCart($event)" @change-num="changeNum($event)"></cart-list>
                <cart-total :list="list"></cart-total>
            </div>
        `,
        components: {
            'cart-title': CartTitle,
            'cart-list': CartList,
            'cart-total': CartTotal
        },
        methods: {
            delCart: function (id) {
                let index = this.list.findIndex(item => {
                    return item.id === id;
                });
                this.list.splice(index, 1);
            },
            changeNum: function (val) {
                if (val.type === 'change') {
                    this.list.some(item => {
                        if (item.id === val.id) {
                            item.num = val.num;
                            return true;
                        }
                    });
                }else if(val.type==='sub'){
                    this.list.some(item => {
                        if (item.id === val.id) {
                            item.num -= 1;
                            return true;
                        }
                    });
                }else if(val.type==='add'){
                    this.list.some(item => {
                        if (item.id === val.id) {
                            item.num += 1;
                            return true;
                        }
                    });
                }
        }
    }
    })
    let app = new Vue({
        el: '#app',
        data: {}
    })
</script>
</body>