Vue.js 2.x笔记:组件(5)

1. 组件简介

  组件(Component)是 Vue.js 最强大的功能之一,组件可以扩展 HTML 元素,封装可重用的代码。

  组件:为了拆分Vue实例的代码量,以不同的组件来划分不同的功能模块,需要什么样的功能,可以去调用对应的组件。

  模块化和组件化的区别:

  ◊ 模块化:是从代码逻辑的角度进行划分的;方便代码分层开发,保证每个功能模块的职能单一。

  ◊ 组件化:是从UI界面的角度进行划分的;前端的组件化,方便UI组件的重用。  

2. 注册组件

  Vue.js提供两种组件注册方式:全局注册和局部注册。

2.1 全局组件

  全局注册需要在根实例初始化之前注册,这样组件才能在任意实例中被使用。

  注册全局组件语法格式:

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>
posted @ 2018-06-12 09:31  libingql  阅读(544)  评论(0编辑  收藏  举报