首页 > 技术文章 > Vue组件

skyflask 2019-06-10 09:52 原文

一、组件概述

1.什么是组件?

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。

所有的 Vue 组件同时也都是 Vue 的实例,所以可接受相同的选项对象 (除了一些根级特有的选项) 并提供相同的生命周期钩子。

  

二、组件定义

1、组件定义步骤

组件定义2步骤:
a、创建组件构造器
Vue.extend()定义要渲染的HTML

b、注册组件
Vue.component()定义组件名和构造器

 

调用Vue.extend()创建的是一个组件构造器,构造器有一个选项对象,选项对象的template属性用于定义组件要渲染的HTML;

调用Vue.component()注册组件时,需要提供2个参数:组件的标签名 和 组件构造器;注册的组件要挂载到某个Vue实例下,否则它不会生效;

Vue.extend() 和 Vue.component():由于 Vue 本身是一个构造函数,Vue.extend() 是一个类继承方法,它用来创建一个 Vue 的子类并返回其构造函数; 而Vue.component() 的作用在于:建立指定的构造函数与 ID 字符串间的关系,从而让 Vue.js 能在模板中使用它;直接向 Vue.component() 传递 options 时,它会在内部调用 Vue.extend()。

  

 

2、全局组件

全局组件定义2个步骤:
// 1.创建组件构造器
  let Profile = Vue.extend({
// 1.1 模板选项
  template: `
    <div>
    <input type="date">
    <p>今天已经是冬天了!</p>
    </div>
    `
 });

// 2. 注册一个全局的组件,任何实例都可以使用它
  Vue.component('my-date', Profile);
全局组件可以直接在每一个vue实例中使用。

 

3、局部组件

// 1.创建组件构造器
  let Profile = Vue.extend({
// 1.1 模板选项
  template: `
    <div>
    <input type="date">
    <p>今天已经是冬天了!</p>
    </div>
    `
  });
//2、注册到一个指定的Vue实例中,这样就只能在某一个实例中使用这个组件了
  new Vue({
    el: '#app',
//通过注册到特定的实例,来达到局部应用,本例中只能应用在id=app的实例中
    components: {
      'my-date': Profile,
    }
    });

 注意:

  • 全局组件需要单独注册;

  • 局部组件必须注册到特定的实例中,以components属性的方式;

 

三、父子组件

// 1. 子组件构造器
        let Child1 = Vue.extend({
            template: `<img src="img/img_01.jpg" width="200">`
        });
let Child2 = Vue.extend({
            template: `<p>我认为自己很美!</p>`
        });
//如果想单独使用子组件,必须单独注册。这样就可以作为一个单独的全局组件使用。
Vue.component('child', Child1);

// 2. 父组件构造器
        Vue.component('parent', {
            components: {
                'my-child1': Child1,
                'my-child2': Child2
            },
            template:
                `
                  <div>
                      <my-child1></my-child1>
                      <my-child2></my-child2>
                  </div>
                `
        });
//3、使用
<div id="app">
        <parent></parent>
    //child为全局组件,可以单独使用!
        <child></child>
    </div>

  

四、组件简化

1、template方式简化

<body>
    <div id="app">
        <my-div></my-div>
    </div>

    <template id="my_div">
    //template里面只能有一个根,也就是所有内容都包含在一个div里面! <div> <div>我是MT!</div> <input type="date"> <img src="img/img_02.jpg" alt="" width="400"> </div> </template> <script src="js/vue.js"></script> <script> // 1. 实例化组件 Vue.component('my-div', { template: '#my_div' }); new Vue({ el: '#app', }); </script> </body>

  

2、script方式简化

<body>
    <div id="app">
        <my-div></my-div>
    </div>

    <script type="text/x-template" id="my_div">
        <div>
            <img src="img/img_02.jpg" alt="" width="200">
            <p>我是花姑娘!</p>
        </div>
    </script>

    <script src="js/vue.js"></script>
    <script>

        // 1. 实例化组件
        Vue.component('my-div', {
            template: '#my_div'
        });

        new Vue({
           el: '#app',
        });
    </script>
</body>

注意事项:

1. 组件挂载数据必须是函数,返回一个对象

 Vue.component('my-btn', {
            template: '#my_btn',
            data(){
                return {
                    counter: 0
                }
            }
        });

  


 组件挂载数据,必须是一个函数,且返回一个对象,这样子组件被任何对象引用时,data就互不干涉,都是一个独立的对象,可以随时修改,数据都互不影响。 

 2、组件的template中,必须包含在一个根下面

 

<template id="my_div">
    //template里面只能有一个根,也就是所有内容都包含在一个div里面!
        <div>
            <div>我是MT!</div>
            <input type="date">
            <img src="img/img_02.jpg" alt="" width="400">
        </div>
    </template>



<template id="my_div">    
       //这是错误的,不能有多个根元素!
            <div>我是MT!</div>
            <input type="date">
            <img src="img/img_02.jpg" alt="" width="400">
    </template>

 

  

 

五、组件间通讯

1、单层通讯

a、父子通信
<body>
    <div id="app">
     //第三步、父组件给子组件传值,单层传值无需动态绑定。父子传值,让子组件更活起来!
        <my-div message="今天要下雨" imgsrc="img/img_02.jpg"></my-div>
        <my-div message="明天要下冰雹" imgsrc="img/img_01.jpg"></my-div>
    </div>

    <template id="my_div">
  //第一步:子组件定义接受的值
        <div>
            <h1>{{message}}</h1>
            <img :src="imgsrc" width="200" alt="">
        </div>
    </template>

    <script src="js/vue.js"></script>
    <script>
        // 1. 创建组件,
  //第二步、通过props定义从父组件能够接受的值
        Vue.component('my-div', {
            template: '#my_div',
            props: ['message', 'imgsrc']//注意,要支持驼峰式imgSrc的写法,需要在template和父组件里面修改成img-src或img-Src。
        });

        new Vue({
           el: '#app',
            data: {
               msg: '今天的天气很好!'
            }
        });
    </script>
</body>

  

2、多层通讯

<body>
//4、祖父组件使用和传值
<div id="app">
    <my-parent :imgtitle="title" :imgsrc="img"></my-parent>
</div>

<template id="my_img">
    <img :src="imgsrc" width="200">
</template>

<template id="my_title">
    <h2>{{title}}</h2>
</template>

//3、父组件定义和传值
<template id="my_parent">
    <div>
        <child1 :childimg="imgsrc"></child1>
        <child2 :childtitle="imgtitle"></child2>
    </div>
</template>

<script src="js/vue.js"></script>
<script>

    // 1. 子组件的实例
    let Child1 = Vue.extend({
        template: '#my_img',
        props: ['childmyimg']  #父组件传递属性
    });

    let Child2 = Vue.extend({
        template: '#my_title',
        props: ['childtitle']
    });

    // 2. 子组件注册到父组件
    Vue.component('my-parent', {
        props: ['imgtitle', 'imgsrc'],  #曾祖父传递的属性
        components: {
            'child1': Child1,
            'child2': Child2
        },
        template: '#my_parent'
    });

    new Vue({
        el: '#app',
        data: {
            title: '我是不是很漂亮',
            img: 'img/img_01.jpg'
        }
    });
</script>
</body>

祖父组件=>父组件=>子组件,注意事项:
1、多层传值必须是动态绑定的方式 :
2、相对子组件必须通过props属性接受父组件传过来的数据

 

六、自定义事件

子组件通知给外界,他执行了什么操作,此时,外界会做出相应的操作。
<body>
    <div id="app">
       <!--父组件通过on监听total事件,一旦执行total事件,则会修改Vue实例中的数据,这样就达到了在父组件中多次调用子组件,数据都是独立的-->
        <my-btn @total="allcounter()"></my-btn>
        <my-btn @total="allcounter()"></my-btn>
        <my-btn @total="allcounter()"></my-btn>
        <my-btn @total="allcounter()"></my-btn>
        <my-btn @total="allcounter()"></my-btn>
        <my-btn @total="allcounter()"></my-btn>
        <my-btn @total="allcounter()"></my-btn>

        <p>所有按钮一共点击了{{totalCounter}}次</p>
    </div>

    <template id="my_btn">
         <button @click="total()">点击了{{counter}}次</button>
    </template>

    <script src="js/vue.js"></script>
    <script>
//定义子组件
        Vue.component('my-btn', {
            template: '#my_btn',
            data(){
                return { 
                    counter: 0  //注意:在子组件中挂载数据,必须返回对象,这样在任何地方调用子组件都是独立的,数据互不干涉。
                }
            },
            methods: {
                total(){
                    this.counter += 1;

                    // 通知外界,我触发了total方法,然后他就会调用后面的allcounter(),这样就把所有的点击次数加起来了
                    this.$emit('total');
                }
            }
        });

        new Vue({
           el: '#app',
            data: {
                totalCounter: 0
            },
            methods: {
               allcounter(){
                   this.totalCounter += 1;
               }
            }
        });
    </script>
</body>

  

 

使用 $on(eventName) 监听事件

 

使用 $emit(eventName) 触发事件

 

七、插槽slot

slot的意思是插槽,其目的在于让组件的可扩展性更强。打个比方说:假如父组件需要在子组件内放一些DOM,那么这些DOM是显示、不显示、在哪个地方显示、如何显示,就是slot分发负责的。

注意:如果使用子组件时,在里面添加DOM,默认这些Dom是不会显示的,因为子组件根本不认识。例如:

<child><div>我想在子组件中显示,但是显示不出来</div></child>

插槽就像电路板,是用来方便插入内容的。slot分为匿名插槽和具名插槽。
匿名插槽就像公交车,谁都可以上,任何人都可以使用这个插槽,很类似娱乐圈中的女明星啦;
具名插槽则是私家车,一个人对应一辆车,一个内容对应一个插槽,这个就是生活中的良家妇女啦。

1、匿名插槽

<body>
    <div id="app">
        <my-slot>
            <!--date会覆盖默认的插槽-->
            <input type="date">
        </my-slot>
    </div>

    <template id="my_slot">
        <div id="panel">
            <h2 class="panel-header">插槽的头部</h2>
            <!--预留一个插槽-->
            <slot>可以替换任何标签,默认显示提示的内容</slot>
            <footer>插槽的尾部</footer>
        </div>
    </template>

    <script src="js/vue.js"></script>
    <script>
        Vue.component('my-slot', {
            template: '#my_slot'
        });

        new Vue({
           el: '#app',
            data: {
               msg: '今天的天气很好!'
            }
        });
    </script>
</body>

  

2、具名插槽

<body>
    <div id="app">
        <my-computer>
 		<!--只能根据slot的name插入对应的数据-->
            <div slot="cpu">Inter Core i8</div>
            <img slot="gpu" src="img/img_02.jpg" width="100" alt="">
            <div slot="memory">128G</div>
            <input type="color" slot="hard-drive">
        </my-computer>
    </div>

    <template id="my_computer">
        <div id="main">
            <slot name="cpu">这里是插cpu的</slot>
            <slot name="gpu">这里是插gpu的</slot>
            <slot name="memory">这里是插内存条的</slot>
            <slot name="hard-drive">这里是插硬盘的</slot>
        </div>
    </template>

    <script src="js/vue.js"></script>
    <script>
        Vue.component('my-computer', {
            template: '#my_computer'
        });

        new Vue({
           el: '#app',
            data: {
               msg: '今天的天气很好!'
            }
        });
    </script>
</body>

  

 

八、综合案例

组件以及父子组件传值

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app">
    <div>
        <input type="text" v-model="inputValue">
        <button @click="handleClickBtn">添加</button>
    </div>
    <ul>
        <todo-item
                v-for="(item,index) in list"
                :content="item"
                :key="item"
                :index="index"
                :msg="msg"
                @delete="handleParentDelete"
        >
            {{item}}

        </todo-item>
    </ul>
</div>

<script src="js/vue.js"></script>
<script>
    //定义全局组件todoItem
    var todoItem = {
        //子组件接受来自父组件的传值,可以直接使用
        props: ['content', 'index', 'msg'],
        template: "<li @click='handleItemDelete'>{{content}}-{{msg}}</li>",
        methods: {
            handleItemDelete: function () {
                //当点击子组件时,会向外触发delete事件,同时带上对应的参数,父组件监听到delete被触发后,则会执行对应的方法
                this.$emit('delete', this.index)
            }
        },
    }
    // 1. 创建Vue的实例
    let vm = new Vue({
        el: '#app',
        data: {
            list:[],
            inputValue:'',
            msg:'我是来自父组件的msg'
        },
        components:{
            //全局组件注册到vm实例中
            todoItem
        },
        methods:{
            handleClickBtn:function(){
                if(this.inputValue){
                    this.list.push(this.inputValue);
                    this.inputValue = '';
                }else{
                    alert('输入内容不能为空!');
                }
            },
            handleParentDelete:function(id){
                //$emit传给父组件的参数,在这里接受。
                alert(id);
                this.list.splice(id,1);
            },

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

 VUE的组件其实也是一个Vue实例,她具备vue的所有属性和方法。

  

九、组件的注意事项

1、is属性

解决h5的一些小bug

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
<div id="app">
    <table>
        <tbody>
            //tbody标签直接使用row会在h5中有问题,解析后会跳到table外面去
            <row></row>
            <row></row>
            <row></row>
            //修改为:
            <tr is="row"></tr>
            <tr is="row"></tr>
            <tr is="row"></tr>
            //ul标签里面需要使用li,select标签中需要使用option
        </tbody>
    </table>
</div>

    <script src="js/vue.js"></script>
    <script>

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

        // 1. 创建Vue的实例
        let vm = new Vue({


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

  

2、子组件的data

 组件中的data必须是一个函数,返回一个对象

 

        data:function(){
                return {
                    number:0
                }
            },        

 

3、ref使用

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <div id="app">
        <counter ref="one" @change="handleChange"></counter>
        <counter ref="two" @change="handleChange"></counter>
        <div>{{total}}</div>
    </div>

    <script src="js/vue.js"></script>
    <script>

        Vue.component('counter',{
            template:'<div @click="handleClick">{{number}}</div>',
            data:function(){
                return {
                    number:0
                }
            },
            methods:{
                handleClick:function(){
                    this.number++
                    this.$emit('change')
                }
            }

        })

        // 1. 创建Vue的实例
        let vm = new Vue({
            el:'#app',
            data:{
                total:0
            },
            methods:{
                handleChange:function () {
                    this.total = this.$refs.one.number + this.$refs.two.number
                }
            }

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

 

  

4、父子传值

父组件传值给子组件,子组件最好不要修改父组件的数据,因为加入父组件传递的是一个对象,可能被多个子组件反复的修改,导致数据混乱。

 

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
   <div id="app">
       <counter :count='1' @change='handleChange'></counter>
       <counter :count='2' @change='handleChange'></counter>
       <span>{{total}}</span>
   </div>

<script src="js/vue.js"></script>
<script>
    var counter={
      props:['count'],
      template:"<div @click='handleClick'>{{number}}</div>",
      data:function(){
        return {
          //拷贝一个父本出来
          number:this.count
        }
      },
      methods:{
        handleClick:function(){
          //注意:避免直接修改父组件传过来的数据
          //this.count++

          //每次步长为1
          this.number += 1
          //每次子组件被点击后都告知父组件触发对应的事件
          this.$emit('change',1)
        }
      }
    }
    // 1. 创建Vue的实例
    let vm = new Vue({
        el: '#app',
        components:{
          counter
        },
        data:{
          total:3
        },
        methods:{
          handleChange:function(num){

            this.total+=num
          }
        }

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

 

5、props和非props特性

 子组件对父组件传过来的数据进行约束检测,则为props特性

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
    <div id="app">
        <child content="hello world"></child>
    </div>

    <script src="js/vue.js"></script>
    <script>

        Vue.component('child',{
            props:{
                content:{
                    type:String,
                    required:false,
                    default:'default value',
                    validator:function (value) {
                        return (value.length>5)
                    }
                }
            },
            template:'<div>{{content}}</div>',

        })

        // 1. 创建Vue的实例
        let vm = new Vue({
            el:'#app',

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

  

props特点:

  • 1、父组件传递过来
  • 2、子组件必须验证
  • 3、子组件可以使用
  • 4、不会渲染在子组件中

父组件向子组件传递数据,但是在子组件没有明确接受,则不能通过{{}}使用,也称之为非props

非props特点:

  • 1、父组件传递过来
  • 2、子组件没明确写在props里面接受
  • 3、子组件不能通过{{}}使用
  • 4、会渲染在子组件当中,比如上面的content='hello wold‘会在子组件元素的html属性中渲染’。

 

6、给组件绑定原生事件

正常情况下,在父组件中绑定事件,我们只能在子组件通过this.$emit('events')进行触发后执行:

<child @click='handleClick'></child>
如果需要在父组件中也生效,需要通过给父组件绑定原生事件:
<child @click.native='handleClick'></child>
完整示例如下:
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
</head>
<body>
   <div id="app">
      <child @click.native='handleClick'></child>
   </div>

<script src="js/vue.js"></script>
<script>
    Vue.component('child',{
      template:'<div>Child</div>',

    })
    // 1. 创建Vue的实例
    let vm = new Vue({
        el: '#app',
        methods:{
          handleClick:function(){
            alert('aaa')
          }
        }

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

  

推荐阅读