[前端] 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