Vuejs全家桶系列(八)--- 组件

简介

什么是组件:组件是Vue.js最强大的功能之一。

组件可以扩展HTML元素,封装可重用的代码。在较高层面上,组件是自定义的元素,Vue.js的编译器为它添加特殊功能。在有些情况下,组件也可以是原生HTML元素的形式,以is特性扩展。

组件的定义

方式一

先创建组件构造器,然后由组件构造器创建组件

//1.使用Vue.extend()创建一个组件构造器
var MyComponent=Vue.extend({
    template:'<h3>Hello World</h3>'
});
//2.使用Vue.component(标签名,组件构造器),根据组件构造器来创建组件
Vue.component('hello',MyComponent);
方式二

直接创建组件(推荐),其实是方式一的简写

Vue.component('my-world',{
    template:'<h1>你好,世界</h1>'
});

问题:如果组件内代码太多,结构就会杂乱难懂。
解决:将代码抽离出来,形成模块。

方式三 引用模板

将组件内容放到模板<template>中并引用

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>引用模板</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <my-hello></my-hello>
        <my-hello></my-hello>
    </div>

<template id="wbs">
    <!-- <template>必须有且只有一个根元素 -->
    <div>
        <h3>{{msg}}</h3>
        <ul>
            <li v-for="value in arr">{{value}}</li>
        </ul>
    </div>
</template>

<script>
    var vm=new Vue({
        el:'#itany',
        components:{
            'my-hello':{
                name:'panini',  //指定组件的名称,默认为标签名,可以不设置
                template:'#wbs',
                data(){
                    return {
                        msg:'帕尼尼的博客',
                        arr:['tom','jack','mike']
                    }
                }
            }

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

问题:在父组件中嵌套定义子组件,使得结构杂乱难懂
解决:把子组件抽离处理

方式四 抽离模板
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>抽离模板</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <hello></hello>
    </div>

<template id="wbs">
    <!-- <template>必须有且只有一个根元素 -->
    <div>
        <h3>{{msg}}</h3>
        <ul>
            <li v-for="value in arr">{{value}}</li>
        </ul>
    </div>
</template>
<script>
    var hello = {
        name:'panini',  //指定组件的名称,默认为标签名,可以不设置
        template:'#wbs',
        data(){
            return {
                msg:'帕尼尼的博客',
                arr:['tom','jack','mike']
            }
        }
    };

    var vm=new Vue({
        el:'#itany',
        components:{
            hello //es6写法:如果键和值一样,可只写一个
        }
    }); 
</script>
</body>
</html>

问题:这使得页面结构杂乱难懂
解决:类似于把css代码抽离成一个.css文件来引用,我们把子组件抽离成.vue文件来引用

方式五 组件抽离成vue文件

注意,这种方式要使用vue-cli工具,以后会介绍
一般开发使用这种方式,这样做,条理清晰。

组件的类型

全局组件

全局组件,可以在所有vue实例中使用

Vue.component('my-hello',{
    template:'<h3>{{name}}</h3>',
    data:function(){ //在组件中存储数据时,必须以函数形式,函数返回一个对象
        return {
            name:'alice'
        }
    }
});
局部组件

局部组件,只能在当前vue实例中使用

var vm=new Vue({
    el:'#itany',
    data:{
        name:'tom'
    },
    components:{ //局部组件
        'my-world':{
            template:'<h3>{{age}}</h3>',
            data(){
                return {
                    age:25
                }
            }
        }
    }
}); 
动态组件

多个组件使用同一个挂载点,然后动态的在它们之间切换
举一个应用场景:登录和注册页面的切换

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>动态组件</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <button @click="flag='my-hello'">显示hello组件</button>
        <button @click="flag='my-world'">显示world组件</button>
        <div>
            <keep-alive>
                <component :is="flag"></component>  
            </keep-alive>
        </div>
    </div>

    <script>
        var vm=new Vue({
            el:'#itany',
            data:{
                flag:'my-hello'
            },
            components:{
                'my-hello':{
                    template:'<h3>我是hello组件:{{x}}</h3>',
                    data(){
                        return {
                            x:Math.random()
                        }
                    }
                },
                'my-world':{
                    template:'<h3>我是world组件:{{y}}</h3>',
                    data(){
                        return {
                            y:Math.random()
                        }
                    }
                }
            }
        }); 
    </script>
</body>
</html>
keep-alive

使用keep-alive组件缓存非活动组件,可以保留状态,避免重新渲染,默认每次都会销毁非活动组件并重新创建

进阶

组件的通信

父子组件

在一个组件内部定义另一个组件,称为父子组件
子组件只能在父组件内部使用
默认情况下,子组件无法访问父组件中的数据,每个组件实例的作用域是独立的

var vm=new Vue({ //根组件
    el:'#itany',
    components:{
        'my-hello':{  //父组件
            data(){},
            template:'#hello',
            components:{
                'my-world':{ //子组件
                    data(){},
                    template:'#world',
                }
            }
        }
    }
}); 
子组件访问父组件的数据

a)在调用子组件时,绑定想要获取的父组件中的数据
b)在子组件内部,使用props选项声明获取的数据,即接收来自父组件的数据
总结:父组件通过props向下传递数据给子组件

父组件访问子组件的数据

a)在子组件中使用vm.$emit(事件名,数据)触发一个自定义事件,事件名自定义
b)父组件在使用子组件的地方监听子组件触发的事件,并在父组件中定义方法,用来获取数据
总结:子组件通过events给父组件发送消息,实际上就是子组件把自己的数据发送到父组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>父子组件及组件间数据传递</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <my-hello></my-hello>
    </div>

    <template id="hello">
        <div>
            <h3>我是hello父组件</h3>
            <h3>访问自己的数据:{{msg}},{{name}},{{age}},{{user.username}}</h3>
            <h3>访问子组件的数据:{{sex}},{{height}}</h3>
            <hr>

            <my-world :message="msg" :name="name" :age="age" @e-world="getData"></my-world>
        </div>
    </template>

    <template id="world">
        <div>
            <h4>我是world子组件</h4>
            <h4>访问父组件中的数据:{{message}},{{name}},{{age}},{{user.username}}</h4>
            <h4>访问自己的数据:{{sex}},{{height}}</h4>
            <button @click="send">将子组件的数据向上传递给父组件</button>
        </div>
    </template>

    <script>
        var vm=new Vue({ //根组件
            el:'#itany',
            components:{
                'my-hello':{  //父组件
                    methods:{
                        getData(sex,height){
                            this.sex=sex;
                            this.height=height;
                        }
                    },
                    data(){
                        return {
                            msg:'网博',
                            name:'tom',
                            age:23,
                            user:{id:9527,username:'唐伯虎'},
                            sex:'',
                            height:''
                        }
                    },
                    template:'#hello',
                    components:{
                        'my-world':{ //子组件
                            data(){
                                return {
                                    sex:'male',
                                    height:180.5
                                }
                            },
                            template:'#world',
                            // props:['message','name','age','user'] //简单的字符串数组
                            props:{ //也可以是对象,允许配置高级设置,如类型判断、数据校验、设置默认值
                                message:String,
                                name:{
                                    type:String,
                                    required:true
                                },
                                age:{
                                    type:Number,
                                    default:18,
                                    validator:function(value){
                                        return value>=0;
                                    }
                                },
                                user:{
                                    type:Object,
                                    default:function(){ //对象或数组的默认值必须使用函数的形式来返回
                                        return {id:3306,username:'秋香'};
                                    }
                                }
                            },
                            methods:{
                                send(){
                                    // console.log(this);  //此处的this表示当前子组件实例
                                    this.$emit('e-world',this.sex,this.height); //使用$emit()触发一个事件,发送数据
                                }
                            }
                        }
                    }
                }
            }
        }); 
    </script>
</body>
</html>

单向数据流

props是单向绑定的,当父组件的属性变化时,将传导给子组件,但是不会反过来
而且不允许子组件直接修改父组件中的数据。

但是如果子组件需要修改父组件传来的数据呢?

应用场景一

如果子组件想把它作为局部数据来使用,可以将数据存入另一个变量中再操作,不影响父组件中的数据

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>单向数据流</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <h2>父组件:{{name}}</h2>
        <input type="text" v-model="name">
        <hr>
        <my-hello :name="name"></my-hello>
    </div>

    <template id="hello">
        <div>
            <h3>子组件:{{name}}</h3>
            <button @click="change">修改数据</button>
        </div>
    </template>

    <script>
        var vm=new Vue({ //父组件
            el:'#itany',
            data:{
                name:'tom',
            },
            components:{
                'my-hello':{ //子组件
                    template:'#hello',
                    props:['name'],
                    data(){
                        return {
                            username:this.name //将数据存入另一个变量中再操作
                        }
                    },
                    methods:{
                        change(){
                            this.username='alice';  //修改这个变量
                        }
                    }
                }
            }
        }); 
    </script>
</body>
</html>

子组件用一个属性接收了父组件传来的值,然后两者之间再无关联

应用场景二

如果子组件想修改数据并且同步更新到父组件,有两个方法

方法一 使用.sync

1.0版本中支持,2.0版本中不支持,2.3版本又开始支持

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>单向数据流</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <h2>父组件:{{name}}</h2>
        <input type="text" v-model="name">
        <hr>
        <my-hello :name.sync="name"></my-hello>
    </div>

    <template id="hello">
        <div>
            <h3>子组件:{{name}}</h3>
            <button @click="change">修改数据</button>
        </div>
    </template>

    <script>
        var vm=new Vue({ //父组件
            el:'#itany',
            data:{
                name:'tom',
            },
            components:{
                'my-hello':{ //子组件
                    template:'#hello',
                    props:['name'],
                    data(){
                        return {
                        }
                    },
                    methods:{
                        change(){
                            this.$emit('update:name','alice');
                        }
                    }
                }
            }
        }); 
    </script>
</body>
</html>

优点:父子组件数据双向绑定
缺点:处理过程比较繁琐,效率不高

方法二 使用对象传参

原理:因为对象是引用类型,指向同一个内存空间。父组件传给子组件的是这个对象的内存地址(类似于C/C++的指针),子组件把内存地址里的数据修改了,父组件也会相应的变化

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>单向数据流</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <h2>父组件:{{user.name}}</h2>
        <button @click="change">修改数据</button>
        <hr>
        <my-hello :user="user"></my-hello>
    </div>

    <template id="hello">
        <div>
            <h3>子组件:{{user.name}}</h3>
            <button @click="change">修改数据</button>
        </div>
    </template>

    <script>
        var vm=new Vue({ //父组件
            el:'#itany',
            data:{
                user:{
                    name:"panini",
                    age:20
                }
            },
            methods:{
                change(){
                    this.user.name = "panini";
                }
            },
            components:{
                'my-hello':{ //子组件
                    template:'#hello',
                    props:['user'],
                    data(){
                        return {
                        }
                    },
                    methods:{
                        change(){
                            this.user.name = "alice";
                        }
                    }
                }
            }
        }); 
    </script>
</body>
</html>

优点:传递多个参数时有绝对的优势,效率很高
缺点:单值传递需要封装成对象先

这不禁让我怀念起了C/C++用&传址传参的好处了

非父子组件的通信

空vue实例实现

非父子组件间的通信,可以通过一个空的Vue实例作为中央事件总线(事件中心),用它来触发事件和监听事件

格式
var Event=new Vue();
Event.$emit(事件名,数据);
Event.$on(事件名,data => {});
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>非父子组件间的通信</title>
    <script src="js/vue.js"></script>
</head>
<body>
    <div id="itany">
        <my-a></my-a>
        <my-b></my-b>
        <my-c></my-c>
    </div>

    <template id="a">
        <div>
            <h3>A组件:{{name}}</h3>
            <button @click="send">将数据发送给C组件</button>
        </div>
    </template>

    <template id="b">
        <div>
            <h3>B组件:{{age}}</h3>
            <button @click="send">将数组发送给C组件</button>
        </div>
    </template>

    <template id="c">
        <div>
            <h3>C组件:{{name}}{{age}}</h3>
        </div>
    </template>

    <script>
        //定义一个空的Vue实例
        var Event=new Vue();

        var A={
            template:'#a',
            data(){
                return {
                    name:'tom'
                }
            },
            methods:{
                send(){
                    Event.$emit('data-a',this.name);
                }
            }
        }
        var B={
            template:'#b',
            data(){
                return {
                    age:20
                }
            },
            methods:{
                send(){
                    Event.$emit('data-b',this.age);
                }
            }
        }
        var C={
            template:'#c',
            data(){
                return {
                    name:'',
                    age:''
                }
            },
            mounted(){ //在模板编译完成后执行
                Event.$on('data-a',name => {
                    this.name=name;
                    // console.log(this);
                });

                Event.$on('data-b',age => {
                    this.age=age;
                });
            }
        }

        var vm=new Vue({
            el:'#itany',
            components:{
                'my-a':A,
                'my-b':B,
                'my-c':C
            }
        }); 
    </script>
</body>
</html>
vuex实现

一般开发使用这种方式,在后面会介绍到

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值