Vue.js 2.x筆記:組件(5)

1. 組件簡介

  組件(Component)是 Vue.js 最強大的功能之一,組件能夠擴展 HTML 元素,封裝可重用的代碼。html

  組件:爲了拆分Vue實例的代碼量,以不一樣的組件來劃分不一樣的功能模塊,須要什麼樣的功能,能夠去調用對應的組件。前端

  模塊化和組件化的區別:vue

  ◊ 模塊化:是從代碼邏輯的角度進行劃分的;方便代碼分層開發,保證每一個功能模塊的職能單一。node

  ◊ 組件化:是從UI界面的角度進行劃分的;前端的組件化,方便UI組件的重用。  數組

2. 註冊組件

  Vue.js提供兩種組件註冊方式:全局註冊和局部註冊。app

2.1 全局組件

  全局註冊須要在根實例初始化以前註冊,這樣組件才能在任意實例中被使用。dom

  註冊全局組件語法格式:ide

Vue.component(tagName, options)

  其中,tagName 爲組件名,options 爲配置選項。模塊化

  這條語句須要寫在var vm = new Vue({ options })以前。函數

  註冊組件後調用方式:

<tagName></tagName>

  全部實例都能用全局組件。

  組件名定義方式:PascalCase和kebab-case。在組件命名時能夠採用PascalCase或kebab-case,但在DOM中只能使用kebab-case。

  PascalCase示例:

<div id="app">
    <my-component></my-component>
</div>
<script>
    Vue.component('MyComponent', {
        template: '<div>標題</div>'
    });

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

  kebab-case示例:

<div id="app">
    <my-component></my-component>
</div>
<script>
    Vue.component('my-component', {
        template: '<div>標題</div>'
    });

    var vm = new Vue({
        el: "#app"
    });
</script>
<div id="app">
    <home></home>
</div>
<script>
    Vue.component("home", {
        template: "<div>{{text}}</div>",
        data: function () {
            return {
                text: "主頁"
            };
        }
    });

    new Vue({
        el: "#app"
    });
</script>
<div id="app">
    <home></home>
</div>
<script>
    var homeTpl = Vue.extend({ 
        template: "<div>{{text}}</div>",
        data: function () {
            return {
                text: "主頁"
            };
        }
    });

    Vue.component('home', homeTpl);

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

  使用template標籤:

<div id="app">
    <home></home>
</div>
<template id="tpl">
    <div>{{text}}</div>
</template>
<script>
    Vue.component("home", {
        template: "#tpl",
        data: function () {
            return {
                text: "主頁"
            };
        }
    });

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

2.2 局部組件

  局部組件只能在被註冊的組件中使用,不能在其餘組件中使用。

<div id="app">
    <home></home>
</div>
<script>
    new Vue({
        el: "#app",
        components: {
            "home": {
                template: "<div>{{text}}</div>",
                data: function () {
                    return {
                        text: "主頁"
                    };
                }
            }
        }
    });
</script>

2.3 Vue.extend

2.3.1 基本使用

<div id="app">
    <home></home>
</div>
<script>
    var home = Vue.extend({
        template: "<div>標題</div>"
    });

    Vue.component("home", home);

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

2.3.2 參數data

  data:在 Vue.extend() 中必須是函數。

<body>
    <task></task>

    <script>
        var task = Vue.extend({
            template:"<div>{{ taskName }}</div>",
            data:function(){
                return {
                    taskName:"任務名稱"
                }
            }
        });

        new task().$mount("task");
    </script>
</body>

2.3.3 使用$mount

  在實例中沒有el選項時,可經過mount掛載。

  mount:掛載,將vue實例掛靠在某個dom元素上的一個過程。

<!DOCTYPE html>
<html>

<head>
    <meta charset="utf-8">
    <title>libing.vue</title>
    <script src="node_modules/vue/dist/vue.min.js"></script>
</head>

<body>
    <div id="app"></div>
    <script>
        var home = Vue.extend({
            template: "<div>標題</div>"
        });

        new home().$mount("#app");
    </script>
</body>

</html>

3. 組件通訊

3.1 props:父組件向子組件傳遞數據

  prop 是組件用來傳遞數據的自定義特性,在組件上註冊自定義屬性。

  prop特性註冊成爲組件實例的屬性。

   props :父組件向子組件傳遞數據。

  一個組件默承認以擁有任意數量的 prop,任何值均可以傳遞給任何 prop。

3.1.1 靜態props

  示例:

<div id="app">
    <home text="主頁"></home>
</div>
<script>
    var homeTpl = Vue.extend({
        props:["text"],
        template: "<div>{{text}}</div>"
    });

    Vue.component('home', homeTpl);

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

3.1.2 動態props

  使用 v-bind 動態綁定 props 的值到父組件的數據中。每當父組件的數據變化時,該變化也會傳導給子組件。

<div id="app">
    <home v-bind:text="text"></home>
</div>
<script>
    var homeTpl = Vue.extend({
        props: ["text"],
        template: "<div>{{text}}</div>"
    });

    Vue.component('home', homeTpl);

    new Vue({
        el: "#app",
        data: {
            text: "主頁"
        }
    });
</script>

  因爲HTML Attribute不區分大小寫,當使用DOM模板時,camelCase的props名稱要轉爲kebab-case。

<div id="app">
    <home warning-text="提示信息"></home>
</div>
<script>
    Vue.component('home', {
        props: ['warningText'],
        template: '<div>{{ warningText }}</div>'
    });

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

  傳遞的數據能夠是來自父級的動態數據,使用指令v-bind來動態綁定props的值,當父組件的數據變化時,也會傳遞給子組件。

<div id="app">
    <home v-bind:warning-text="warningText"></home>
</div>
<script>
    Vue.component('home', {
        props: ['warningText'],
        template: '<div>{{ warningText }}</div>'
    });

    var vm = new Vue({
        el: "#app",
        data: {
            warningText: '提示信息'
        }
    });
</script>

注:prop 是單向傳遞,當父組件的屬性變化時,將傳遞給子組件,可是不會反過來。這是爲了防止子組件無心修改了父組件的狀態。

  示例:

<template>
  <li>{{ id }}-{{ text }}</li>
</template>
<script>
export default {
  name: "TodoItem",
  props: ["id", "text"]
};
</script>
TodoItem.vue
<template>
  <ul>
    <TodoItem
      v-for="item in list"
      :key="item.id"
      :id="item.id"
      :text="item.text"
    ></TodoItem>
  </ul>
</template>
<script>
import TodoItem from "./TodoItem";

export default {
  name: "TodoList",
  components: {
    TodoItem
  },
  data: function() {
    return {
      list: [
        {
          id: 1,
          text: "To Do"
        },
        {
          id: 2,
          text: "In progress"
        },
        {
          id: 3,
          text: "Done"
        }
      ]
    };
  }
};
</script>
TodoList.vue
<template>
  <div id="app">
    <TodoList />
  </div>
</template>

<script>
import TodoList from './views/TodoList'

export default {
  name: 'App',
  components: {
    TodoList
  }
}
</script>
App.vue

3.1.3 props驗證

  爲組件的 prop 指定驗證要求,若是有一個需求沒有被知足,則 Vue 會在控制檯中警告。

Vue.component('my-component', {
    props: {
        // 基礎的類型檢查 (`null` 匹配任何類型)
        propA: Number,
        // 多個可能的類型
        propB: [String, Number],
        // 必填的字符串
        propC: {
            type: String,
            required: true
        },
        // 帶有默認值的數字
        propD: {
            type: Number,
            default: 100
        },
        // 帶有默認值的對象
        propE: {
            type: Object,
            // 對象或數組且必定會從一個工廠函數返回默認值
            default: function () {
                return {
                    message: 'hello'
                }
            }
        },
        // 自定義驗證函數
        propF: {
            validator: function (value) {
                // 這個值必須匹配下列字符串中的一個
                return ['success', 'warning', 'danger'].indexOf(value) !== -1
            }
        }
    }
});

  類型檢查:type能夠是下列原生構造函數中的一個:String、Number、Boolean、Array、Object、Date、Function、Symbol,也能夠是一個自定義的構造函數,而且經過 instanceof 來進行檢查確認。

  示例:

<div id="app">
    <parent-component></parent-component>
</div>

<template id="child-component1">
    <h2>{{ message }}</h2>
</template>
<template id="child-component2">
    <h2>{{ message }}</h2>
</template>
<template id="parent-component">
    <div>
        <child-component1></child-component1>
        <child-component2></child-component2>
    </div>
</template>

<script>
    Vue.component('parent-component', {
        template: '#parent-component',
        components: {
            'child-component1': {
                template: '#child-component1',
                data() {
                    return {
                        message: '子組件1'
                    };
                }
            },
            'child-component2': {
                template: '#child-component2',
                data() {
                    return {
                        message: '子組件2'
                    };
                }
            }
        }
    });

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

  示例:

<div id="app">
    <todo :todo-data="taskList"></todo>
</div>

<template id="tpl-todo-item">
    <li>{{ id }} - {{ text }}</li>
</template>

<template id="tpl-todo-list">
    <ul>
        <todo-item v-for="item in todoData" :id="item.id" :text="item.text"></todo-item>
    </ul>
</template>

<script>
    // 構建一個子組件
    var todoItem = Vue.extend({
        template: "#tpl-todo-item",
        props: {
            id: {
                type: Number,
                required: true
            },
            text: {
                type: String,
                default: ''
            }
        }
    })

    // 構建一個父組件
    var todoList = Vue.extend({
        template: "#tpl-todo-list",
        props: {
            todoData: {
                type: Array,
                default: []
            }
        },
        // 局部註冊子組件
        components: {
            todoItem: todoItem
        }
    })

    // 註冊到全局
    Vue.component('todo', todoList)

    new Vue({
        el: "#app",
        data: {
            taskList: [{
                    id: 1,
                    text: 'New'
                },
                {
                    id: 2,
                    text: 'InProcedure'
                },
                {
                    id: 3,
                    text: 'Done'
                }
            ]
        }
    });
</script>

3.2 自定義事件:子組件向父組件傳遞數據

  每個Vue實例都實現事件接口:

  $on(eventName):監聽事件

  $emit(eventName) :觸發事件,自定義事件。推薦始終使用 kebab-case 的事件名。

  子組件須要向父組件傳遞數據時,子組件用$emit(eventName)來觸發事件,父組件用$on(eventName)來監聽子組件的事件。

  示例1:

<template>
  <div>
    <button @click="onparent">子組件觸發父組件</button>
  </div>
</template>
<script>
export default {
  methods: {
    onparent() {
      this.$emit("onchild");
    }
  }
};
</script>
Child.vue
<template>
  <div>
    <Child @onchild="inparent"></Child>
  </div>
</template>
<script>
import Child from "./Child";
export default {
  components: {
    Child
  },
  methods: {
    inparent() {
      console.log("父組件響應了");
    }
  }
};
</script>
Parent.vue
<template>
  <div id="app">
    <Parent />
  </div>
</template>

<script>
import Parent from './views/Parent'

export default {
  name: 'App',
  components: {
    Parent
  }
}
</script>
App.vue

  示例2:

<div id="app">
    <searchbar></searchbar>
</div>

<template id="tpl-search-form">
    <div class="input-group form-group" style="width: 500px;">
        <input type="text" class="form-control" placeholder="請輸入查詢關鍵字" v-model="keyword" />
        <span class="input-group-btn">
            <input type="button" class="btn btn-primary" value="查詢" @click="search">
        </span>
    </div>
</template>
<template id="tpl-search-bar">
    <searchform @onsearch="search"></searchform>
</template>

<script>
    // 構建一個子組件
    var searchform = Vue.extend({
        template: "#tpl-search-form",
        data: function () {
            return {
                keyword: 'libing'
            };
        },
        methods: {
            search: function () {
                this.$emit('onsearch', this.keyword);
            }
        }
    });

    // 構建一個父組件
    var searchbar = Vue.extend({
        template: "#tpl-search-bar",
        components: {
            searchform: searchform
        },
        methods: {
            search(keyword) {
                console.log(keyword);
            }
        }
    })

    // 註冊到全局
    Vue.component('searchbar', searchbar);

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

  購物車示例:

<div id="app">
    <shoppingcart :shopppingcarts="products" @calc="getTotal"></shoppingcart>
    <div>總計:{{ totalPrice }}</div>
</div>
<template id="shoppingcart">
    <table>
        <tr>
            <th>商品ID</th>
            <th>商品名稱</th>
            <th>單價</th>
            <th>數量</th>
        </tr>
        <tr v-for="item in shopppingcarts">
            <td>{{ item.ID }}</td>
            <td>{{ item.ProductName }}</td>
            <td>{{ item.UnitPrice }}</td>
            <td><input type="text" v-model="item.Quantity" @change="calcTotal" /></td>
        </tr>
    </table>
</template>
<script>
    var shoppingcart = Vue.extend({
        template: "#shoppingcart",
        props: ["shopppingcarts"],
        methods: {
            calcTotal: function () {
                this.$emit("calc");
            }
        }
    });

    new Vue({
        el: "#app",
        components: {
            shoppingcart: shoppingcart
        },
        data: {
            totalPrice: 100,
            products: [{
                ID: 1,
                ProductName: "手機",
                UnitPrice: 1000,
                Quantity: 2
            }, {
                ID: 2,
                ProductName: "電腦",
                UnitPrice: 5000,
                Quantity: 5
            }]
        },
        methods: {
            getTotal() {
                console.log(new Date());
                this.totalPrice = 0;
                this.products.forEach(product => {
                    this.totalPrice += product.UnitPrice * product.Quantity;
                });
            }
        },
        mounted() {
            //當vue執行完畢以後,去執行函數
            this.getTotal();
        }
    });
</script>

3.3 EventBus:非父子組件通訊

  非父子組件包括:兄弟組件、跨級組件。

  經過實例化一個Vue對象 (如:const bus = new Vue() ) 做爲總線,在組件中經過事件傳遞參數( bus.$emit(event, [...args]) ),再在其餘組件中經過bus來監聽此事件並接受參數( bus.$on(event, callback) ),從而實現通訊。

  示例:

  bus.js

import Vue from 'vue'

const bus = new Vue();

export default bus;

  Send.vue

<template>
  <div class="send">
    <h1>發送參數:{{msg}}</h1>
    <button @click="send">發送</button>
  </div>
</template>
<script>
import bus from "../utils/bus.js";

export default {
  data() {
    return {
      msg: "Hello World"
    };
  },
  methods: {
    send() {
      bus.$emit("receive", this.msg);
    }
  }
};
</script>

  Receive.vue

<template>
  <div class="receive">
    <h1>接收參數:{{msg}}</h1>
  </div>
</template>
<script>
import bus from "../utils/bus.js";

export default {
  data() {
    return {
      msg: "Hello"
    };
  },
  created() {
    bus.$on("receive", param => {
      this.msg = param;
    });
  },
  beforeDestroy() {
    bus.$off("receive");
  }
};
</script>

  App.vue

<template>
  <div id="app">
    <Send></Send>
    <Receive></Receive>
  </div>
</template>

<script>
import Send from './views/Send'
import Receive from './views/Receive'

export default {
  name: 'App',
  components: {
    Send,
    Receive
  }
}
</script>
相關文章
相關標籤/搜索