[前端] VUE基础 (4) (组件基础、局部组件、全局组件、父子传值、平行传值)

一、组件

1.组件概念

我们将一个页面看成一个最大的组件(app),这个组件里面又由多个子组件构成。这样形成一颗组件树。如上图所示;

例如,你可能会有页头、侧边栏、内容区等组件,每个组件又包含了其它的像导航链接、博文之类的组件。

为了能在模板中使用,这些组件必须先注册以便 Vue 能够识别。

这里有两种组件的注册类型:全局注册和局部注册

2.template的优先级

在前面章节的vue使用中,我们的vue实例与一个<div id="app">标签绑定,使用的是"el"属性来绑定。

vue实例绑定模板,还有另外一种方式:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Component</title>
</head>
<body>
    <div id="app">
        el:<h2>{{ msg }}</h2>
    </div>
    <script src="./static/vue.js"></script>
    <script>
        var vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"hello"
                }
            },
            template:`
                <div class="app">
                    <h2>temp:{{ msg }}</h2>
                </div>
            `
        })
    </script>
</body>
</html>

执行结果:

可以看到,实际绑定的模板是template属性定义的模板。

所以,template属性定义的模板的绑定优先级高于el属性指定的模板。

注意:虽然template可以定义模板,但是el所指定的模板也不可少,相当于template模板替换了el指定的模板(el可以说是指定了替换的位置),其实实际原因和声明周期有关系。

二、局部组件的定义、挂载和使用

局部组件的主要流程就是三步:

  • 定义组件
  • 挂载组件
  • 使用组件

1.组件的定义

// 1.定义组件
let App = {
    data(){
        return {
            msg:'我是组件'
        }
    },
    template:`
        <div class="App_component">
            <h2>{{ msg }}</h2>
        </div>
    `
}

注意,组件名要么使用首字母大写(驼峰命名),例如AppComponent,要么使用"-"命名形式,例如:app-component,必须与html的普通标签区分开。组件是一个对象,和vue实例的参数对象差不多,但是没有"el"。

注意:组件的template必须要用一个闭合标签包起来,例如用一个<div>包起来,例如:

template:`
    <h2>{{ msg }}</h2>
    <h2>{{ msg }}</h2>
`

则会报错。

2.挂载组件

挂载组件,就是将定义好的组件挂载到vue实例中。

var vm = new Vue({
    el:"#app",
    data(){
        return {
            msg:"hello"
        }
    },
    // 2.挂载组件
    components:{
        App  // 这里相当于App:App,key个value一致的话,在ES6中可以只写一个
    }
})

3.使用组件

var vm = new Vue({
    el:"#app",
    data(){
        return {
            msg:"hello"
        }
    },
    // 2.挂载组件
    components:{
        App  // 这里相当于App:App,key个value一致的话,在ES6中可以只写一个
    },
    // 3.使用组件
    template:`
        <div class="app">
            <App></App>
        </div>
    `
})

注意,使用组件的时候,标签名就是组件的名称(这就是为什么组件名首字母要用大写,为了区分普通标签)。

当我们使用<App>的时候,就相当于把组件里的template替换到这个位置。然后又被替换到了el:"#app"对应的页面位置。如下图所示:

可以对比一下HTML代码中,id="app"的div不存在,因为已经被class="app"的div替换了(也就是vue实例的template),然后该div中的<App>也被class="App_component"的div所替代(也就是组件的template)。

 

注意,使用<App>的时候,可以在vue实例的template中使用,也可以在el对应的标签中去使用(此时就不能有template属性了,因为他的优先级高于el):

<body>
<div id="app">
    <App></App>
</div>
<script src="./static/vue.js"></script>
...
...

4.组件定义、挂载、使用完整代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Component</title>
</head>
<body>
    <div id="app">
        el:<h2>{{ msg }}</h2>
    </div>
    <script src="./static/vue.js"></script>
    <script>
        // 定义组件
        let App = {
            data(){
                return {
                    msg:'我是组件'
                }
            },
            template:`
                <div class="App_component">
                    <h2>{{ msg }}</h2>
                </div>
            `
        }
        var vm = new Vue({
            el:"#app",
            data(){
                return {
                    msg:"hello"
                }
            },
            // 2.挂载组件
            components:{
                App  // 这里相当于App:App,key个value一致的话,在ES6中可以只写一个
            },
            // 3.使用组件
            template:`
                <div class="app">
                    <App></App>
                </div>
            `
        })
    </script>
</body>
</html>

5.嵌套组件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Component</title>
    <style>
        .App_component{
            background-color: darkseagreen;
            height: 400px;
            width: 500px;
        }
        .Vheader{
            background-color: cornflowerblue;
            height: 100px;
            width: 500px;
        }
    </style>
</head>
<body>
<div id="app">

</div>
<script src="./static/vue.js"></script>
<script>
    // 定义VHeader
    let Vheader ={
        data() {
            return {
                msg: '我是App中的Vheader'
            }
        },
        template: `
            <div class="Vheader">
                <h3>{{ msg }}</h3>
            </div>
        `
    }
    // 定义App组件
    let App ={
        data() {
            return {
                msg: '我是App组件'
            }
        },
        template: `
            <div class="App_component">
                <Vheader></Vheader>
            </div>
        `,
        components:{
            Vheader
        }
    }
    var vm = new Vue({
        el: "#app",
        data() {
            return {
                msg: "hello"
            }
        },
        // 2.挂载组件
        components: {
            App // 这里相当于App:App,key个value一致的话,在ES6中可以只写一个
        },
        //3.使用组件
        template: `
            <div class="app">
                <App></App>
            </div>
        `
    })
</script>
</body>
</html>

上述代码中,App组件挂载在vue实例中,在vue实例的template中使用。而Vheader组件挂载在App组件中,在App组件的template中使用。

三、全局组件

1.全局组件的定义和使用

无需挂载,vue实例或者局部组件都可以直接使用的组件。

<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script>
    // 定义一个全局组件,一个按钮
    Vue.component('VBtn', {
        data() {
            return {
                name: '全局组件中的按钮'
            }
        },
        template: `
                <button>{{ name }}</button>
            `
    })
    var vm = new Vue({
        el: "#app",
        data() {
            return {
                msg: 'hello world'
            }
        },
        // VBtn为全局组件,可以在这里直接使用,而无需挂载
        template: `
            <div>
                <VBtn></VBtn>
            </div>
        `
    })
</script>
</body>

使用Vue.component()来定义,定义出的组件可以不用挂载直接使用。

实现效果:

2.slot分发数据

在1.的全局组件VBtn中,按钮的名称是根据自己data中的name属性来指定的,如果想在被使用的地方来指定,则需要使用slot分发。

<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script>
    // 定义一个全局组件,一个按钮
    Vue.component('VBtn', {
        data() {
            return {
                name: '全局组件中的按钮'
            }
        },
        template: `
                <button><slot></slot></button>
            `
    })
    var vm = new Vue({
        el: "#app",
        data() {
            return {
                msg: 'hello world',
                btn_name:'vue实例中的VBtn按钮'
            }
        },
        // VBtn为全局组件,可以在这里直接使用,而无需挂载
        template: `
            <div>
                <VBtn>{{btn_name}}</VBtn>
            </div>
        `
    })
</script>
</body>

使用<slot>标签,就可以将使用时指定的数据分发到组件中。这里相当于将vue实例中的btn_name属性的数据分发到了VBtn组件中,并放在<slot>标签的位置。

实现效果:

四、父组件传值给子组件

1.父组件传递值出去

var vm = new Vue({
    el: "#app",
    data() {
        return {
            msg: 'hello world',
            btn_name:'vue实例中的VBtn按钮'
        }
    },
    // 在VBtn中我们绑定一个属性trans_data,通过这个属性将msg的值传递给子组件VBtn
    template: `
        <div>
            <VBtn :trans_data="msg">{{btn_name}}</VBtn>  
        </div>
    `
})

在父组件中,绑定一个自定义的属性,将msg属性的值传递出去。

如果只是传递静态值,则直接使用自定义属性就可以传递了:

<VBtn trans_data="hello world">{{btn_name}}</VBtn>  

2.子组件接收父组件传递的值

// 定义一个全局组件,一个按钮
Vue.component('VBtn', {
    data() {
        return {
            name: '全局组件中的按钮'
        }
    },
    // 子组件中接收trans_data中的值(就是父组件中msg的值)
    props:['trans_data'],
    template: `
            <div>
                <button><slot></slot></button>
                <button>{{trans_data}}</button>
            </div>
        `
})

使用props列表来接收父组件传递的值,注意,props列表中的字符串代表着接收属性的名称,必须与父组件中绑定属性的名称一致。

3.父组件传值给子组件完整代码

<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script>
    // 定义一个全局组件,一个按钮
    Vue.component('VBtn', {
        data() {
            return {
                name: '全局组件中的按钮'
            }
        },
        // 子组件中接收trans_data中的值(就是父组件中msg的值)
        props:['trans_data'],
        template: `
                <div>
                    <button><slot></slot></button>
                    <button>{{trans_data}}</button>
                </div>
            `
    })
    var vm = new Vue({
        el: "#app",
        data() {
            return {
                msg: 'hello world',
                btn_name:'vue实例中的VBtn按钮'
            }
        },
        // 在VBtn中我们绑定一个属性trans_data,通过这个属性将msg的值传递给子组件VBtn
        template: `
            <div>
                <VBtn :trans_data="msg">{{btn_name}}</VBtn>
            </div>
        `
    })
</script>
</body>

值传递流程:父组件data中的msg--->父组件template中VBtn中的trans_data属性--->子组件props中的'trans_data'--->子组件template中的{{trans_data}}。

四、子组件传值给父组件

1.子组件向父组件传出值

// 定义一个全局组件,一个按钮
Vue.component('VBtn', {
    data() {
        return {
            name: '全局组件中的按钮'
        }
    },
    // 子组件中接收trans_data中的值(就是父组件中msg的值)
    props:['trans_data'],
    // 1.在<button>中绑定click时间,触发kidClickHandler事件函数
    template: `
            <div>
                <button @click="kidClickHandler">msg翻转</button>
            </div>
        `,
    methods:{
        // 2.kidClickHandler事件函数使用$emit触发父组件实例上的事件,事件名是parentClickHandler,传递参数是翻转后的trans_data
        kidClickHandler(){
            this.$emit('parentClickHandler',this.trans_data.split('').reverse().join(''));
        }
    }
})

1)子组键实现onclick事件绑定,触发kidClickHandler事件函数

2)在kidClickHandler事件函数中使用$emit触发父组件的parentClickHandler事件,并传递参数(翻转后的trans_data)

2.父组件接收子组件传递的值

var vm = new Vue({
    el: "#app",
    data() {
        return {
            msg: 'hello world',
        }
    },
    // 在VBtn中我们绑定一个属性trans_data,通过这个属性将msg的值传递给子组件VBtn
    template: `
        <div>
            <VBtn :trans_data="msg" @parentClickHandler="processClickHandler"></VBtn>
            <h2>{{msg}}</h2>
        </div>
    `,
    // 3.当子组件的kidClickHandler被触发时,$emit会触发parentClickHandler事件,即会执行processClickHandler事件函数
    methods:{
        processClickHandler(inversed_msg){
            // 4.将从子组件传递过来的翻转后的trans_data赋值给msg。完成msg的翻转,并更新到<h2>{{msg}}</h2>中
            this.msg = inversed_msg;
        }
    }
})

1)父组件的parentClickHandler被触发,调用processClickHandler事件函数

2)processClickHandler事件函数接收子组件传递的参数(翻转后的trans_data)

3)processClickHandler事件函数,将接收的参数赋值给msg属性

4)由于<h2>中使用msg属性,则msg会自动更新

3.组件传值给父组件完整代码

<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script>
    // 定义一个全局组件,一个按钮
    Vue.component('VBtn', {
        data() {
            return {
                name: '全局组件中的按钮'
            }
        },
        // 子组件中接收trans_data中的值(就是父组件中msg的值)
        props:['trans_data'],
        // 1.在<button>中绑定click时间,触发kidClickHandler事件函数
        template: `
                <div>
                    <button @click="kidClickHandler">msg翻转</button>
                </div>
            `,
        methods:{
            // 2.kidClickHandler事件函数使用$emit触发父组件实例上的事件,事件名是parentClickHandler,传递参数是翻转后的trans_data
            kidClickHandler(){
                this.$emit('parentClickHandler',this.trans_data.split('').reverse().join(''));
            }
        }
    })
    var vm = new Vue({
        el: "#app",
        data() {
            return {
                msg: 'hello world',
            }
        },
        // 在VBtn中我们绑定一个属性trans_data,通过这个属性将msg的值传递给子组件VBtn
        template: `
            <div>
                <VBtn :trans_data="msg" @parentClickHandler="processClickHandler"></VBtn>
                <h2>{{msg}}</h2>
            </div>
        `,
        // 3.当子组件的kidClickHandler被触发时,$emit会触发parentClickHandler事件,即会执行processClickHandler事件函数
        methods:{
            processClickHandler(inversed_msg){
                // 4.将从子组件传递过来的翻转后的trans_data赋值给msg。完成msg的翻转,并更新到<h2>{{msg}}</h2>中
                this.msg = inversed_msg;
            }
        }
    })
</script>
</body>

实现效果:

五、平行组件传值

平行组件传值,能够代替父子传值,用得比较多一些。

1.定义一个公交车对象

该公交车对象主要用来承载平行组件之间的参数传递。他是一个vue对象。

// 1.定义一个bus对象(vue对象,用来承载平行组件的数据传递)
let bus = new Vue();

2.Test1数据发送者

// 定义Test1全局组件
Vue.component('Test1', {
    data() {
        return {
            msg: 'Test1中的值'
        }
    },
    // 2.在<button>中绑定click时间,触发trans_data_to_Test2事件函数
    template: `
            <div>
                <button @click="trans_data_to_Test2">传递值给Test2</button>
            </div>
        `,
    methods:{
        // 3.trans_data_to_Test2事件函数,触发bus中的testData事件函数(这个名字需要和接收方Test2中$on中的事件函数名一致)
        trans_data_to_Test2(){
            bus.$emit('testData',this.msg);
        }
    }

1)button绑定click事件,触发trans_data_to_Test2事件函数

2)trans_data_to_Test2调用bus的testData事件函数(该函数不需要单独去定义,理解为一个名字标识即可),并将要传递的数据作为参数传入

3.Test2数据接收者

// 定义Test2全局组件
Vue.component('Test2', {
    data() {
        return {
            msg: ''
        }
    },
    template: `
            <div>
                 这里是Test2组件,msg的值为:{{msg}}
            </div>
        `,
    // 4.给bus绑定$on函数,即可获取Test1使用$emit传递到testData事件函数中的参数
    created(){
        // 5.这里必须使用箭头函数,否则this指向的是bus,而我们是要给Test2的msg赋值。使用箭头函数,this指向的是调用bus的实例,也就是Test2
        bus.$on('testData',(val)=>{
            this.msg = val;
        })
    },
    methods:{}
})

1)在created函数中,给bus绑定$on,在这里从testData中获取val参数中的数据

2)将获取的数据复制给自己的msg属性,这里注意,在$on中的回调函数,必须使用箭头函数,让this指向Test2组件,而不是bus

4.平行组件传值完整代码(Test1-->Test2)

<body>
<div id="app">
</div>
<script src="./static/vue.js"></script>
<script>
    // 1.定义一个bus对象(vue对象,用来承载平行组件的数据传递)
    let bus = new Vue();
    // 定义Test1全局组件
    Vue.component('Test1', {
        data() {
            return {
                msg: 'Test1中的值'
            }
        },
        // 2.在<button>中绑定click时间,触发trans_data_to_Test2事件函数
        template: `
                <div>
                    <button @click="trans_data_to_Test2">传递值给Test2</button>
                </div>
            `,
        methods:{
            // 3.trans_data_to_Test2事件函数,触发bus中的testData事件函数(这个名字需要和接收方Test2中$on中的事件函数名一致)
            trans_data_to_Test2(){
                bus.$emit('testData',this.msg);
            }
        }
    })
    // 定义Test2全局组件
    Vue.component('Test2', {
        data() {
            return {
                msg: ''
            }
        },
        template: `
                <div>
                     这里是Test2组件,msg的值为:{{msg}}
                </div>
            `,
        // 4.给bus绑定$on函数,即可获取Test1使用$emit传递到testData事件函数中的参数
        created(){
            // 5.这里必须使用箭头函数,否则this指向的是bus,而我们是要给Test2的msg赋值。使用箭头函数,this指向的是调用bus的实例,也就是Test2
            bus.$on('testData',(val)=>{
                this.msg = val;
            })
        },
        methods:{}
    })
    var vm = new Vue({
        el: "#app",
        data() {
            return {

            }
        },
        // 在VBtn中我们绑定一个属性trans_data,通过这个属性将msg的值传递给子组件VBtn
        template: `
            <div>
                <Test1></Test1>
                <Test2></Test2>
            </div>
        `
    })
</script>
</body>

Test1(数据发送者)和Test2(数据接受者)都是全局组件,并且都在vue根实例中使用,他们两是平行关系(父子关系也可以使用平行传值)。

平行传值的核心就是利用一个中间vue实例(bus公交车实例)来实现数据的传递。不同于父子传值。父子之间有特定的关系,所以传递值不需要借助中间实例。

 

66

posted @ 2020-02-06 21:20  风间悠香  阅读(928)  评论(0编辑  收藏  举报