组件及组件间的通信
本文是我在学习过程中记录学习的点点滴滴,目的是为了学完之后巩固一下顺便也和大家分享一下,日后忘记了也可以方便快速的复习。
Spring MVC数据绑定和响应
- 前言
- 一、全局组件的注册及使用
-
- 1.1、组件是什么?
- 1.2、全局组件的注册及使用
- 二、组件的再认识
-
- 2.1、体会全局组件的使用
- 2.2、用 template 标签来定义模板
- 2.3、自定义组件中的 data 选项
- 三、局部组件的注册及使用
-
- 3.1、局部组件基本用法
- 3.2、把组件定义简化为一个对象写法
- 四、动态组件的应用
-
- 4.1、内置组件 的应用
- 4.2、内置组件 的应用
- 4.3、is 特殊特性的使用
- 4.4、text/X-Template 类型应用
-
- 4.4.1、后端返回的对象数组
- 五、父子组件
-
- 5.1、父子组件如何定义及使用
- 5.2、每个组件的作用域是独立的
- 5.3、子组件访问父组件中的数据(当props是数组时)
-
- 5.3.1、当props是对象时
- 5.4、父组件访问子组件中的数据
- 5.5、如何使父组件数据修改了子组件数据跟着改变
- 5.6、如何使子组件数据修改了父组件数据跟着变化
- 5.7、 通过 solt 组件分发内容
- 5.8、用 v-solt 指令替代 slot
- 5.9、作用域插槽的应用举例
- 5.10、使用 ref 获取子组件对象[数据、方法]
- 六、 非父子组件间的通信
前言
今天学习的主要是关于握全局组件、局部组件的注册及使用,动态组件及 is 特色特性的使用,灵活进行组件间的通信,v-slot 指令及作用域插槽的使用的知识的理解和应用
一、全局组件的注册及使用
总结:主要是将一部分的区域的代码写在一个组件template里,还要注册,并且可以设置别名,当需要调用这部分代码的时候就直接调用(将该template的别名当作标签调用)。
1.1、组件是什么?
组件是 vue.JS 的强大功能之一(vue 核心思想:数据驱动、组件化),组件用来扩展 html 元素,封装可重用代码,可以说,组件是自定义 html 标签。组件本质是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,例如 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是 el 选项,它是根实例特有的选项。
例如,在一个系统的绝大多数的网页中,网页都包含 header、menu、body、footer 等等部分,在很多时候,同一个系统中的多个页面,可能仅仅是页面中 body 部分显示的内容不同,因此,我们就可以将系统中重复出现的页面元素设计成一个个的组件,当我们需要使用到的时候,引用这个组件即可。在 Vue 中创建一个新的组件之后,为了能在模板中使用,这些组件必须先进行注册以便 Vue 能够识别。在 Vue 中有两种组件的注册类型:全局注册和局部注册。
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue)新创建的 Vue 根实例,也包括其组件树中的所有子组件的模板中使用;而局部注册的组件只能在当前注册的 Vue 实例中进行使用。
1.2、全局组件的注册及使用
方式一:
先使用 Vue.extend 方法构建模板对象,然后通过
Vue.component 方法来注册我们的组件,因为,组件最后会被解析成自定义的 HTML 代码,因此,我们可以直接在 html 中把注册后的组件名当做标签来使用。
<div id="app"><hello></hello></div><script>//1.创建模板对象,利用 Vue 的全局方法 extendvar MyComponent=Vue.extend({template:'hello world
' //这里面可以写得复杂});//2.根据模板对象注册组件,利用 Vue 的 componet 方法Vue.component('hello',MyComponent) ;//第一个参数为组件名,也就是标签名;第二个参数为模板对象var vm = new Vue({el: '#app', data: {}})</script>
结果:
从控制台中可以看到,我们自定义的组件已经被解析成了
HTML 元素。
`注意:当我们采用 Camel 的方式命名组件时,在使用这个组件的时候,需要将大写字母改成小写字母,同时,两个单词之间需要使用 - 进行连接。如:(该组件名为myHello)
方法二:
直接在 Vue.component 中以一种类似 C# 中的匿名对象的方式直接注册全局组件。【实际就是将上面前面 2 个步骤合并】
<div id="app"><my-hello></my-hello><my-hello2></my-hello2></div><script>Vue.component('myHello', Vue.extend({template: 'hello world
' //这里面可以写得复杂}))//下面这种写法更简单Vue.component('myHello2', {template: 'hello vue
' })var vm = new Vue({el: '#app', //这个 vue 实例实际上也是一个 vue 组件,称之为根组件 Rootdata: {}})</script>
二、组件的再认识
概述:
作用:
总结:template标签里面一定是div包裹着其他标签,否则会报错。
下面两种更简便的写法
Vue.component('myHello', Vue.extend({template: '<h3>hello world</h3>' //这里面可以写得复杂}))//下面这种写法更简单Vue.component('myHello2', {template: '<h3>hello vue</h3>'})
2.1、体会全局组件的使用
<div id="app"><my-hello></my-hello><my-hello2></my-hello2></div><div id="app2"><my-hello></my-hello><my-hello2></my-hello2></div><script>Vue.component('myHello', Vue.extend({template: 'hello world
' //这里面可以写得复杂}))//下面这种写法更简单Vue.component('myHello2', {template: 'hello vue
'})var vm = new Vue({el: '#app',//这个 vue 实例实际上也是一个 vue 组件,称之为根组件 Rootdata: {}})var vm2 = new Vue({el: '#app2',//这个 vue 实例实际上也是一个 vue 组件,称之为根组件 Rootdata: {}})</script>
2.2、用 template 标签来定义模板
在前面的例子中,只是在 template 属性中定义了一个简单的
html 代码,在实际的使用中,template 属性指向的模板内容可能包含很多的元素,而使用 Vue.extend 创建的模板必须有且只有一个根元素,因此,当需要创建具有复杂元素的模板时,你可以在最外层再套一个 div。
<div id="app"><my-hello></my-hello></div><template id="tmp1"><div><h3>hello vue</h3><h4>徐照兴欢迎您</h4></div></template><script>Vue.component('myHello', {template: '#tmp1' })var vm = new Vue({el: '#app',//这个 vue 实例实际上也是一个 vue 组件,称之为根组件 Rootdata: {}})</script>
结果:
2.3、自定义组件中的 data 选项
组件是可复用的 Vue 实例,所以它们与 new Vue 接收相同的选项,除了 template 选项之外,还有 data、computed、watch、methods 以及生命周期钩子等。仅有的例外是像 el 这样根实例特有的选项不能在自定义组件中定义。但是用法上会有些区别,比如data 选项必须是一个函数,通过函数去返回对象。
<div id="app"><my-hello></my-hello></div><template id="tmp1"><div><h3>hello vue</h3><h4>{{msg}}</h4><input type="button" value="单击我" @click="tanchu" /></div></template><script>Vue.component('myHello', {template: '#tmp1',data: function () { //此处组件中 data 必须是一个函数,通过函数返回对象return {msg: "徐照兴欢迎您"}},methods: {tanchu: function () {alert(this.msg)}}})var vm = new Vue({el: '#app', data: {name:”xzx”}})</script>
结果:
全局注册的组件可以用在其被注册之后的任何 (通过 new Vue) 新创建的 Vue 根实例【先注册、再 new Vue】, 也包括其组件树中的所有子组件的模板中使用
三、局部组件的注册及使用
概述:
作用:
总结:
3.1、局部组件基本用法
所谓局部组件就是只能在某个实例中使用。注册局部组件也是在某个实例中通过 components 选项注册
<div id="app"><my-hello></my-hello></div><template id="tmp1"><div><h3>hello vue</h3><h4>{{msg}}</h4><input type="button" value="单击我" @click="tanchu" /></div></template><script>var vm = new Vue({el: '#app', data: {},components: {myHello: {template: '#tmp1',data: function () { //此处 data 必须是一个函数,通过函数返回对象return {msg: "徐照兴欢迎您"}},methods: {tanchu: function () {alert(this.msg)}}}}})</script>
3.2、把组件定义简化为一个对象写法
把组件简化为一个对象,这样在多个 vue 实例中注册写法就会更简单。主要针对局部组件这样写,全局组件就没必要了
<div id="app"><my-hello></my-hello></div><template id="tmp1"><div><h3>hello vue</h3><h4>{{msg}}</h4><input type="button" value="单击我" @click="tanchu" /></div></template><script>var zujian={myHello: {template: '#tmp1', data: function () { //此处 data 必须是一个函数,通过函数返回对象return {msg: "徐照兴欢迎您"}},methods: {tanchu: function () {alert(this.msg)}}}}var vm = new Vue({el: '#app', data: {},components: zujian //不要用引号引起来})</script>
四、动态组件的应用
概念:
所谓动态组件就是多个组件在同一个位置显示,但不是同时显示,比如满足一定条件时显示 A 组件,满足其他条件显示 B 组件。即是说多个组件使用同一个挂载点,然后动态的在多个组件之间切换。
4.1、内置组件 的应用
作用:
使得单击不同的按钮却在同一位置显示不同内容,就行导航栏的那些按钮一样,点击不同的按钮,下面的主页在同一位置显示不同的内容。
需要用到内置的组件,依 is 的值,来决定哪个组件被渲染。is 的值是哪个组件名称就显示哪个组件。
<div id="app"><button @click="currentComponent='my-hello'">显示hello 组件</button><button @click="currentComponent='my-world'">显示world 组件</button><div><!-- --><component :is="currentComponent"></component></div></div><script>var vm = new Vue({ //这个 vue 实例实际上也是一个 vue 组件,称之为根组件 Rootel: '#app', data: {currentComponent:"my-hello"},// 定义局部组件,在某 vue 实例内部定义,通过 components属性定义components:{'my-hello':{template:'我是 hello 组件
', data(){return {x:Math.random() //产生一个随机数作为 x 的值}}},'my-world':{template:'我是 world 组件
', data(){return {y:Math.random() //产生一个随机数作为 y 的值}}}}})</script>
结果:
运行效果:单击【显示 hello 组件】就是 hello 组件内容,单击【显示 world 组件】就是 world 组件内容
现在上述代码的两个组件内容后面分别加上变量 x 和 y
template:‘我是 hello 组件{{x}}
’,
template:‘我是 world组件{{y}}
’,
运行时效果如下图所示。每次切换组件时都会销毁非活动组件并重新创建,效率比较低。
那么如何缓存非活动组件内容,也即是保存非活动组件的状态,避免每次切换重新渲染 。应用内置组件
4.2、内置组件 的应用
作用:
该标签的作用就是缓存页面的内容,使得你在多个页面切换时其中页面的数据依旧保留的是上次离开时的数据。
使用 keep-alive 组件包裹 compontent 组件,就可以缓存非活动组件内容,也即是保存非活动组件的状态,避免每次切换重新渲染,改进上述代码:把上面的代码(compontent 组件)用内置组件keep-alive 包裹起来,其他地方不改,如下:
再次运行:
4.3、is 特殊特性的使用
作用:
使得、
、
和 中的子元素当作模板时使用is特性就可以使用这些元素来包裹他们的子元素。
总结:
诸如、
、
和 中的子元素要
当作模板来使用,就要使用 is 特性。
因为这些元素的子元素使用时需要用这些元素包起来才符合W3C标准,这些子元素使用了is他们的父元素才会生效。Vue和平常的不一样,单独使用这些元素去包裹他们的子元素时在控制台界面并不能显示,控制台中并没有这些元素的身影,必须让其子元素使用了is特性,这些元素才会生效。
例如:<table><component-tr></component-tr><component-tr></component-tr><component-tr></component-tr></table>
或
两者最终得出的结果都是没有table元素包裹它的子元素,加了table标签等于没加:
正确示例:<body> <div id="app"> <table> <!-- --> <tr is='component-tr'></tr> <tr is='component-tr'></tr> <tr is='component-tr'></tr> </table> </div> <script> var componentTr={ template:'
' } new Vue({ el:"#app", data:{ currentComponent:'my-hello' }, components:{ 'component-tr':componentTr } }) </script></body> 学号 姓名 结果:
4.4、text/X-Template 类型应用
上面例子中定义模板时是为模板直接设置字符串,如下黄色底纹代码,也称之为内联模板字符串写法:
var componentTr = {
template: ‘学号 {{msg}} ’
}
缺点是如果模板的 html 代码比较多,写起来不方便,因为不好换行。另一种定义模板的方式是写在一个 元素中,并为其带上 text/x-template 的类型,然后通过一个 id 将模板引用过去。其实就相当于前面全局组件中的内置组件的写法,功能一样哦,通用的。
注意:x-template 需要定义在 Vue 所属的 DOM 元素外,与 dom 元素并列位置。
还可以在注册组件时添加 data 属性,然后就可以引用 data 属性的数据,如下:
<div id="app"><table><tr is='component-tr'></tr><tr is='component-tr'></tr><tr is='component-tr'></tr></table></div><script type="text/x-template" id="tmp1"><tr><td>{{num}}</td><td>{{name}}</td></tr></script><script>var componentTr = {template: '#tmp1', data(){return{num:"1001", name:"徐照兴"}}}var vm = new Vue({el: "#app",data: {msg: "hello"},components: {'component-tr': componentTr}})</script>
结果:
4.4.1、后端返回的对象数组
提问:如果数据是后端返回过来的一个数据集合,比如对象数组(实际开发中就是这样),如何处理动态显示出所有数据,请看下面代码。
<div id="app"><table><tr is='component-tr'></tr></table></div><script type="text/x-template" id="tmp1">//<div><tr v-for="item in stuList"><td>{{item.num}}</td><td>{{item.name}}</td></tr></div></script><script>var componentTr = {template: '#tmp1', data() {return {stuList: [{ num: '1001', name: 'zhangsan' }, { num: '1002', name: 'lisi' }, { num: '1003', name: 'wangwu' }]}}}var vm = new Vue({el: "#app", data: {msg: "hello"},components: {'component-tr': componentTr}})</script>
五、父子组件
5.1、父子组件如何定义及使用
概念:在一个组件内部定义了另一个组件,称为父子组件。
子组件只能在父组件的内部使用。
<div id="app"><my-comp1></my-comp1><!-- --></div><script type="text/x-template" id="comp1"><div><h3>我是 comp1 父组件</h3><hr><my-comp2></my-comp2></div></script><template id="comp2"><div><h3>我是 comp2 子组件</h3></div></template><script>var vm = new Vue({ //根组件el: '#app', data: {},components: {//父组件'my-comp1': {data() {return {msg: '小豆子学堂', name: 'zhangsan', age: 25, user: { id: 1001, username: 'lisi' }}},template: '#comp1', // 定义 my-comp1 组件的子组件【组件中定义组件】components: {//子组件'my-comp2': {template: '#comp2' }}}}})</script>
运行结果:
5.2、每个组件的作用域是独立的
在组件中访问自己组件中的数据是没问题的,但是默认情
况下,子组件无法访问到父组件中的数据,当然父组件也无法访问到子组件中的数据,每个组件实例的作用域是独立的。
那么组件间是如何进行数据传递/通信呢?看下面~5.3、子组件访问父组件中的数据(当props是数组时)
(1)在调用子组件时,绑定想要获取的父组件的数据
(2)在子组件的内部,使用 props 选项声明获取的数据,即接收来自父组件的数据
经过上面 2 个步骤,子组件访问父组件中的数据就变成了获取的是 自己组件中的数据,因为父组件中的数据已经传递给了子组件。
<div id="app"><my-comp1></my-comp1><!-- --></div><template id="comp1"><div><h3>我是 comp1 父组件</h3><h3>访问自己组件中的数据:{{msg}} {{name}} {{age}} {{user.username}}</h3><hr><my-comp2 :message="msg"></my-comp2></div></template><template id="comp2"><div><h3>我是 comp2 子组件</h3><!--
访问父组件中的数据:{{msg}},{{name}},{{age}},{{user.username}}
--><h3>访问父组件 msg 数据:{{message}}</h3></div></template><script>var vm = new Vue({ //根组件el: '#app', data: {},components: {//父组件'my-comp1': {data() {return {msg: '小豆子学堂', name: 'zhangsan', age: 25, user: { id: 1001, username: 'lisi' }}},template: '#comp1', // 定义 my-comp1 组件的子组件components: {//子组件'my-comp2': {template: '#comp2', // (2)在子组件的内部,使用 props 选项声明获取的数据,即接收来自父组件的数据,采用字符串数组形式【这是简单形式,下节深入】props:['message']}}}}})</script>结果:
5.3.1、当props是对象时
当props是对象时就与数组不一样,对象时可以设置好多属性,例如:type,default,required,validator等等。
上面例子中使用的简单用法,即使用字符串数组形式。
props 可以是数组或对象,用于接收来自父组件的数据。props可以是简单的数组,或者使用对象作为替代,对象允许配置高级选项,如类型检测、自定义验证和设置默认值。
你可以基于对象的语法使用以下选项:
type:
可以是下列原生构造函数中的一种:String、Number、Boolean、Array、Object、Date、Function、任何自定义构造函数、或上述内容组成的数组。设置 type 就会检查一个 prop 是否是给定的类型,否则抛出警告。
default: any
为该 prop 指定一个默认值。如果该 prop 没有被传入,则换
做用这个值。对象或数组的默认值必须从一个工厂函数返回。
required: Boolean
定义该 prop 是否是必填项。在非生产环境中,如果这个值为true 且该 prop 没有被传入的,则一个控制台警告将会被抛出。
validator: Function
自定义验证函数会将该 prop 的值作为唯一的参数代入。在非生产环境下,如果该函数返回一个 false 的值 (也就是验证失败),一个控制台警告将会被抛出。<body> <div id="app"> <my-comp1></my-comp1> </div> <script type="text/x-template" id="comp1"> <div> <h3>我是comp1父组件</h3> <h3>访问自己组件中的数据:{{msg}} {{name}} {{age}} {{user.username}}</h3><hr> <my-comp2 :message="msg" :name="name" :age="age"></my-comp2> </div> </script> <template id="comp2"> <div> <h3>我是comp2子组件</h3> <h3>访问父组件中的数据:{{message}} {{name}} {{age}} {{user.username}}</h3> </div> </template> <script>new Vue({//根组件 el:"#app", data:{ currentComponent:'my-hello' }, components:{ 'my-comp1':{//父组件 template:"#comp1", data(){ return{msg:'小豆子学堂',name:'zhangshan',age:25,user:{id:'1001',username:'lisi'} } }, components:{ 'my-comp2':{//子组件template:'#comp2',// props:['message','name','age','user'],props:{ message:String,表示传入的message必须是 String 类型,如果需要更多的设置,设置值又可以是对象,如对name 设置如下: name:{ type:String, required:true//表示必须传入 }, age:{ type:Number, default:18,//表示没有传入默认值为 18 validator:function(value){//validator 必须是一个函数,value 表示传入过来的值 return value>0 } }, user:{ type:Object, default:function(){ return{ id:0000,username:'xzx' } } }},data(){ return{ name2:"xuzhaoxing" }} } } } } }) </script></body>
总结:
子组件访问父组件中的数据,在调用子组件时,绑定想要
获取的父组件的数据(类似:msg=“msg”),然后在子组件中通过props 选项去接收父组件中的数据。
总结:组件中的 props 经常用于将父组件值传递到子组件或是 将 Vue 实例中的属性值传递到组件中使用。 在父组件/Vue 实例引用子组件的时候,通过属性绑定的方式 (v-bind 简写:),将需要传递给子组件的数据进行传递,从而在子 组件内部,通过绑定的属性值获取到父组件/Vue 实例的数据
5.4、父组件访问子组件中的数据
总结:
子组件通过事件给父组件发送消息,实际上就是子组件把自己的数据发送到父组件。父子组件通过 this.$emit方法里第一个参数连接起来,第一个参数是一个父组件的方法,第二个参数是要传的数据,调用this.$emit方法时,第一个参数(父祖件方法)也会执行,它负责接受子组件传过来的值,传过来后data里面的有相应的数据接受传过来的值,此时就相当于父组件也有子组件的值了,此时只需要父组件调用自己的data里数据就好了。子——>父 props
父——>子 this.$emit思路:
把子组件中的数据发送到父组件中去
(1)在子组件中使用 vm.$emit(事件名,要发送的数据)触发一个自定义事件,事件名自定义。要发送的多个数据,直接用逗号隔开即可
(2)父组件在使用子组件的地方侦听子组件触发的事件,并在父组件中定义方法获取数据<body> <div id="app"> <my-comp1></my-comp1> </div> <template id="comp1"> <div> <h3>我是 comp1 父组件</h3> <h3>访问自己组件中的数据: {{msg}} {{name}} {{age}} {{user.username}}</h3> <h4>访问子组件中的数据:{{sex}},{{height}}</h4> <hr> <my-comp2 :message="msg" :name="name" :age="age " :user="user" @send-data="getData"></my-comp2> </div> </template> <template id="comp2"> <div> <h3>我是 comp2 子组件</h3> <h3>访问父组件 msg 等数据: {{message}},{{name}},{{age}},{{user.username}}</h3> <h4>子组件访问自己的数据:{{sex}},{{height}}</h4> <button @click="send">将子组件中的数据向上传递给父 组件</button> </div> </template> <script> var vm = new Vue({ //根组件 el: '#app', data: { }, components: {//父组件 'my-comp1': { data() { return {msg: '小豆子学堂', name: 'zhangsan', age: 25, user: { id: 1001, username: 'lisi' }, sex: '',//要先定义好用来接收数据的属性height: 0 //要先定义好用来接收数据的属性,这里设置了默认值为 0 } }, template: '#comp1', methods: { // 子组件通过事件 sendData 将数据发送过来了,所以这里 getData 定义 2 个形参接受传过来的数据,形参名可以任意 getData(sex1, height1) {this.sex = sex1;//把接收到的数据赋给预先定义好的data 中的属性this.height = height1; } },// 定义 my-comp1 组件的子组件 components: {//子组件 'my-comp2': {template: '#comp2', data() { return { sex: 'male', height: 178 }}, props: ['message', 'name', 'age', 'user'], methods: { send() { //console.log(this);//此处 this 表示的不是vm 实例,而是当前的子组件实例,也就是 my-comp2 组件 //2.1 使用 this.$emit()触发一个事件(事件名建议用-连接,每个单词首字母小写),发送数据 this.$emit('send-data', this.sex, this.height); }} } } } } }) </script></body>
结果:
运行过程分析:单击【将子组件中的数据向上传递给父组件】按钮时触发 send 方法,在 send 方法中通过 emit 方法触发一个事件send-data(命名也很关键哦,建议不要有大写字母),传送数据。在父组件使用子组件地方侦听触发的事件 send-data,并把 getData方法赋给 send-data(也就是说侦听 send-data 事件后做 getData事情,也即执行 getData 方法,这个方法自然定义在父组件中),通过 getData 方法参数把接收到的数据赋给父组件中预先定义好的相应属性。
5.5、如何使父组件数据修改了子组件数据跟着改变
前面说完父子组件之间数据的传递问题,那么他们之间能不能互相修改数据呢?比如说父组件的数据给了子组件后,如果父组件中的数据修改了,子组件中的数据会不会自动修改呢?再比如子组件的数据发送给了父组件,那么子组件中的数据修改了,父组件中对应数据会不会自动修改呢?
所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑
定:父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。
简单说:prop 是单向绑定的,当父组件的属性变化时,将传导给子组件,但是反过来不行,也就是说子组件的属性变化时,不会传导给父组件。演示父组件数据修改了,子组件获取到父组件对应的数据也修改了(代码如下):
<div id="app"><h4>父组件:请输入需要传递给子组件的 title 值:{{title}}<input type="text" v-model="title" /></h4><child-node v-bind:parenttitle="title"></child-node></div><template id="child"><div><h4>子组件:实时获取父组件 Vue 实例中的属性 title 值:{{parenttitle}}</h4></div></template><script>var vm = new Vue({el: '#app', data: {title: '大家好' },components: {'childNode': {template: '#child', props: ['parenttitle']}}});禁止盗版 翻录课程 仅供学习</script>
结果:
查看运行效果:父组件数据修改了,子组件获取到父组件对应的数据也修改了
那么如何做到子组件的数据修改了父组件跟着变化呢?5.6、如何使子组件数据修改了父组件数据跟着变化
子组件把数据发送(this.$emit)给父组件(父组件访问得到子组件的数据),但是子组件的数据改变了,父组件相应的数据不会跟着改变?
思路:
(1)父组件中使用子组件地方要绑定子组件对应数据属性,并用.sync 修饰符(1.0 版本中支持,2.0 版本不支持,2.3 版本又开始支持)(:name.sync=“name”);
(2)子组件中通过 watch 去监听子组件属性的变化,如果变化了,通过 this.$emit 去修改父组件对象的属性值,格式 this.$emit(‘update:要修改的属性名’,’属性值’),除了红色地方是自己定义之外,其他地方固定写法。重要代码(两处):
:name.sync=“name”(第三行)watch: {
name(newVal) {
this.$emit(‘update:name’, newVal)
}
}(倒数第九行)<div id="app"><h4>父组件获取子组件中的 name 属性值:{{name}}</h4><child-node @send-data="getData" :name.sync="name"></child-node></div><template id="child"><div><h4>子组件 name 属性值:{{name}}</h4><button @click="send">将子组件中的数据向上传递给父组件</button><button @click="change">修改子组件 name 属性值</button></div></template><script>var vm = new Vue({el: '#app', data: {title: '大家好', name: '' },methods: {// 子组件通过事件 sendData 将数据发送过来了,所以这里getData 定义 1 个形参接受传过来的数据,形参名可以任意getData(name1) {this.name = name1;//把接收到的数据赋给预先定义好的 data 中的属性}},components: {'childNode': {data() {return {name: '徐照兴' }},template: '#child', methods: {send() {//1 使用 this.$emit()触发一个事件(事件名建议用- 连接,每个单词首字母小写),发送数据this.$emit('send-data', this.name);},change() {this.name = "xzx"//这里的 this 表示的是子组件childNode,this.parenttitle 表示的就是 props 拿到的 title 数据,然后把 title 数据修改为 hello}},watch: {name(newVal) {this.$emit('update:name', newVal)}}}}});</script>
结果:
单击【修改子组件 name 属性值】,父组件获取到的 name属性值也跟着改变。
5.7、 通过 solt 组件分发内容
总结:
我们的目的就是要将自定义组件内的内容显示出来,这里就要用到solt 组件。我们在父组件内使用子组件时需要在子组件内定义一个solt属性,与之对应的在子组件的模板中也需要定义一个solt属性(插槽),这样就能把你在父组件使用子组件时定义在子组件内的内容,插入到与之对应的在子组件的模板中定义solt属性(插槽)的那个位置中去。如果在子组件内的内容没有定义solt属性,那么这些内容就将映射到定义了solt属性但没有名字的插槽中去。
【通俗来讲就是将定义在子组件内的内容通过solt属性来映射到模板中去显示出来】前面自定义组件,使用自定义组件时如下:组件中间都没有写任何内容。那么可以写内容吗?当然可以。
引出问题:
<body> <div id="app"> <panel>123456</panel> </div> <template id="panelTpl"> <div class="panel"> <div class="panel-header">头部标题</div> <div class="panel-body"> 暂无内容 </div> <div class="panel-bottom">更多 点赞</div> </div> </template> <script> var panelTpl = { template: '#panelTpl' } var vm = new Vue({ el: '#app', components: { "panel": panelTpl } }); </script></body>
结果:
上述发现了自定义组件 panel 中输入了内容“123456”但发现并没有显示出来,那么如何获取中间的内容并显示出来呢?——使用插槽 slot使用 slot 组件,获取到的内容(使用自定义组件中的内容)想放到什么位置就把写到自定义模板的什么位置。
元素作为组件模板之中的内容分发插槽。 元素自身将被替换。slot 中文意思:位置,槽。现假设 panel 组件需要多次使用,而且每个 panel 里面的内容都应该不一样,内容可能是文本、图片、超链接、ul 等等 dom 元素内容,如何把 panel 组件中的内容传到 panel-body 中呢,也就是要把每个panel 里的内容插到 panel-body 的 div 中,因此 panel-body 中要有一个插槽,即把“暂无内容”换为插槽 。
<body> <style> .panel { margin: 10px; width: 150px; border: 1px solid #ccc; } .panel-header, .panel-bottom { height: 30px; background-color: antiquewhite; } .panel-body { /* 最小高度 */ min-height: 50px; } </style> <div id="app"> <panel> <p>大家好,欢迎来到徐照兴课堂,有什么不理解的欢迎随时 咨询</p> </panel> <panel> <div>欢迎学习 vue.js</div> </panel> <panel>后续还有更多实战课程哦</panel> </div> <template id="panelTpl"> <div class="panel"> <div class="panel-header">头部标题</div> <div class="panel-body"> <slot></slot> </div> <div class="panel-bottom">更多 点赞</div> </div> </template> <script> var panelTpl = { template: '#panelTpl' } var vm = new Vue({ el: '#app', components: { "panel": panelTpl } }); </script></body>
结果:
现 在 假 如 所 有 的 头 部 标 题 也 要 能 灵 活 的 更 换 , 因 此panel-header 的 div 中内容也要换为插槽,以便灵活插入,为了区分中间部分的插槽,就需要给这里的插槽取名。
默认头部内容【具名 slot】。中间“默认头部内容”表示使用组件地方没有传入内容,采用这里设置的默认内容。部分代码如下<body> <div id="app"> <panel> <span>11111</span> <p>大家好,欢迎来到徐照兴课堂,有什么不理解的欢迎随时咨询</p> <h3 slot="title">教育部</h3> <a href="http://www.baidu.com" slot="more">百度</a> </panel> <panel> <h3 slot="title">教育厅</h3> <div>欢迎学习vue.js</div> <a href="http://www.jd.com" slot="more">京东</a> </panel> <panel> 后续还有更多实战课程哦 </panel> </div> <template id="panelTpl"> <div class="panel"> <div class="panel-header"><slot name="title">默认头部内容</slot></div> <div class="panel-body"> <slot></slot> </div> <div class="panel-bottom"><slot name="more">默认底部内容</slot></div> </div> </template> <script>var panelTpl={ template:'#panelTpl'} var vm = new Vue({ el: '#app', data: { title: '大家好', name: '' }, components: { 'panel': panelTpl } }) </script></body>
结果:
以上就做到了公共的样式一样(需要变化内容地方安放插槽),插槽内容可以通过在使用组件的时候传入。
总结:
插槽模板是 slot,它是一个空壳子,因为它显示与隐藏以及最后用什么样的 html 模板显示由父组件控制。但是插槽显示的位置由子组件自身决定,slot 写在组件 template 的哪块,父组件传过来的模板将来就显示在哪块。这样就使组件可复用性更高,更加灵活。我们可以随时通过父组件给子组件加一些需要的东西。5.8、用 v-solt 指令替代 slot
总结:
道理和上面一样,只不过 slot的写法发生了改变而已。从 vue@2.6.x 开始,Vue 为具名和范围插槽引入了一个全新
的语法,即:v-slot 指令。目的就是想统一 slot 和 scope-slot 语法,使代码更加规范和清晰。从 vue@2.6.0 开始,官方推荐我们使用 v-slot 来替代 slot 和 scope-slot。
v-slot 只能添加到 或自定义组件上,这点与弃用的 slot 属性不同。
对上面的例子进行改进,通过包裹自定义组件的每部分内容,slot 属性可以放到template 上。
插槽的名字现在通过 v-slot:slotName 这种形式来使用。没有名字的 隐含有一个 “default” 名称<div id="app"> <panel> <template v-slot:title> <h3>教育部</h3> </template> <template v-slot:default> <p>大家好,欢迎来到徐照兴课堂,有什么不理解的欢迎随 时咨询</p> </template> <template v-slot:more> <a href="http://www.baidu.com">百度</a> </template> </panel> <panel> <template v-slot:title> <h3>教育厅</h3> </template> <template v-slot:default> <div>欢迎学习 vue.js</div> </template> <template v-slot:more> <a href="http://www.jd.com">京东</a> </template> </panel> <panel> <template v-slot:default> 后续还有更多实战课程哦 </template> </panel> </div>
5.9、作用域插槽的应用举例
总结:
1、通过在插槽上绑定子组件的数据,然后通过 v-slot指令来传递数据,这样在父组件内就可以使用子组件的数据了。
这里讲的就是通过另一种方法让父组件可以使用子组件的数据。
2、v-slot也有缩写:
v-slot:user可以缩写为#user
3、这里的v-bind="userInfo"不能缩写噢,因为缩写的是
v-bind:xxx这种格式的,并不是v-bind=xxx这种格式的有时候父组件需要对子组件的内容进行加工处理,普通插槽不好满足需求,这时候,作用域插槽就派上用场了。作用域插槽允许你传递一个模板而不是已经渲染好的元素给插槽。之所以叫做”作用域“插槽,是因为模板虽然是在父级作用域中渲染的,却能拿到子组件的数据。
使用步骤:
(1)在子组件的 template 模板定义中通过 slot 定义插槽位置(也就是后续父组件中使用子组件的 template 模板的插入位置),并通过 v-bind 把子组件内部数据“绑定”到插槽上。
(2)在父组件中使用子组件,通过 v-slot 指令接收子组件传递上来的数据。<body> <div id="app"> <current-user> <template v-slot:default="userInfo"> 通过父组件位置获取到子组件的用户姓名:{{userInfo.name}} </template> </current-user> </div> <template id="child"> <div> 显示子组件自己的数据:{{userInfo.name}} <br> <slot v-bind="userInfo"></slot> </div> </template> <script> var vm = new Vue({ el: '#app', data: { }, components: { 'current-user': { template: '#child', data() { return {userInfo: { name: '徐照兴'} } } }, } }) </script></body>
结果:
说明:跟 v-on 和 v-bind 一样,v-slot 也有缩写,即把参数之 前的所有内容 (v-slot:) 替换为字符 #。例如 v-slot:header 可以被 重写为 #header
5.10、使用 ref 获取子组件对象[数据、方法]
总结:
1、这里的原理很简单,就和使用ref获取dom元素一样的,给子组件定义一个ref,通过ref就可以获取到这个组件。
2、下面要实现的就是单击父组件的获取元素值,既可以获取元素值,还可以执行子组件的方法来获取时间。这里就是通过ref获取到子组件,然后执行子组件的方法来获取时间。前面讲过使用 ref 可轻松获取页面的 DOM 元素,当我们需
要获取子组件时,同样只需要在需要使用的子组件上添加 ref 属性即可。在下面的示例代码中,我添加了一个子组件,当我们点击 Vue实例上的按钮时,会先调用子组件的方法,然后获取子组件的数据。 因此使用 ref 是父组件获取子组件数据、调用子组件的方法的有效手段。<body> <div id="app"> <input type="text" ref="msgText" v-model="msg"> <button @click="getElement">获取元素值</button> <hr> <child ref="childComponent"></child> </div> <template id="child"> <div> <input type="text" name="datetime" v-model="local"> <button @click="getLocalDate">获取当前时间</button> </div> </template> <script> var vm = new Vue({ el: '#app', data: { msg:'徐照兴' }, methods: { getElement(){ console.log('input输入框的值为:'+this.$refs.msgText.value) this.$refs.childComponent.getLocalDate() console.log('子组件input输入框的值为:'+this.$refs.childComponent.local) } }, components: { 'child': { template: '#child', data() { return {local:'' } }, methods:{ getLocalDate(){var date=new Date();this.local=date.toLocaleDateString() } } }, } }) </script></body>
结果:
单击【获取当前时间】会在左边文本框显示系统当前时间。
可以看到,当我们将 ref 添加到子组件上,我们就可以在 Vue实例上获取到这个注册的组件引用,同注册的 DOM 元素一样,我们都可以使用添加的 ref 属性值作为 key 获取到注册的对象。此时,我们就可以获取到这个子组件上的 data 选项和 methods 选项六、 非父子组件间的通信
三个组件并列关系,并且第三个组件是没有数据的,现在
第三个组件假设要拿到第一个、第二个组件的数据,怎么办呢?
其核心还要第一个、第二个组件要把数据发送给第三个组件的。思路:
var busvm=new Vue();——创建一个空的 vue 实例
busvm.$emit(‘事件名’,data);——触发事件,发送数据
busvm.$on(‘事件名’,data=>{});——侦听事件,接收数据
这种方法实现非父子组件间的通信也只能是针对简单的需求,如果复杂的话需要使用状态管理模式(Vuex)来实现——后面讲。<body> <div id="app"> <my-comp1></my-comp1> <my-comp2></my-comp2> <my-comp3></my-comp3> </div> <template id="tmpl1"> <div> <h3>第一个组件:{{name}}</h3> <button @click="send">将数据发送给第三个组件</button> </div> </template> <template id="tmpl2"> <div> <h3>第二个组件:{{age}}</h3> </div> </template> <template id="tmpl3"> <div> <h3>第三个组件:{{name}}</h3> </div> </template> <script> //定义一个空的 vue 实例,用于发送和侦听事件 var busvm = new Vue(); var vm = new Vue({ el: "#app", components: { //下面 3 个组件是并列关系,不是父子关系 'my-comp1': { template: '#tmpl1', data() { return {name: 'zs' } }, methods: { send() {// this.$emit()//this 表示的是 my-comp1 本身的。不行的busvm.$emit('data-1', this.name);//用于触发事件,第一个参数为事件名,第二个参数为要发送的数据 } } }, 'my-comp2': { template: '#tmpl2', data() { return {age: 20 } } }, 'my-comp3': { template: '#tmpl3', data() { return {name: ''//先初始化 name,为空即可 } }, mounted() {//该钩子函数在模板编译完成之后执行(也就是挂载之后);也就是接受事件的时机 busvm.$on('data-1', name => {this.name = name;// this 表示自身组件(my-comp3),即左边的 name 指自身组件(my-comp3)的属性(所以先通过上面青绿色底纹代码初始化 name 属性),右边 name 为传递过来的数据 });//用于侦听自定义事件。第一个参数为要侦听的事件;第二个参数为回调函数,回调函数的参数为触发事件发送过来的数据,这里用箭头函数,ES6 语法 } } } }) </script></body>
结果:
注意:上面回调函数只能用箭头函数形式来写,如果用 function(name)不行的,因为 this 的含义变了 busvm.$on('data-1',function(name){ this.name=name;//this 表示的是 busvm 实例,可 以通过 console.log(this)查看 });
同理,我想把第二个组件的数据发送给第三个组件。
<div id="app"><my-comp1></my-comp1><my-comp2></my-comp2><my-comp3></my-comp3></div><template id="tmpl1"><div><h3>第一个组件:{{name}}</h3><button @click="send">将数据发送给第三个组件</button></div></template><template id="tmpl2"><div><h3>第二个组件:{{age}}</h3><button @click="send">将数据发送给第三个组件</button></div></template><template id="tmpl3"><div><h3>第三个组件:{{name}} {{age}}</h3></div></template><script>//定义一个空的 vue 实例,用于发送和侦听事件var busvm=new Vue();var vm = new Vue({el:"#app", components:{//下面 3 个组件是并列关系,不是父子关系'my-comp1':{template:'#tmpl1', data(){return {name:'zs' }},methods:{send(){// this.$emit()//this 表示的是 my-comp1 本身的。不行的busvm.$emit('data-1',this.name);//用于触发事件,第一个参数为事件名,第二个参数为要发送的数据}}},'my-comp2':{template:'#tmpl2', data(){return {age:20}},methods:{send(){// this.$emit()//this 表示的是 my-comp2 本身的。不行的busvm.$emit('data-2',this.age);//用于触发事件,第一个参数为事件名,第二个参数为要发送的数据}}/* mounted(){busvm.$emit('data-2',this.age);} */},'my-comp3':{template:'#tmpl3', data(){return {name:'',//先初始化 name,为空即可age:''//先初始化 age,为空即可}},mounted(){//该钩子函数在模板编译完成之后执行(也就是挂载之后);也就是接受事件的时机busvm.$on('data-1',name=>{this.name=name;//this 表示自身组件(my-comp3),即左边的 name 指自身组件的属性,右边 name 为传递过来的数据});//用于侦听自定义事件。第一个参数为要侦听的事件;第二个参数为回调函数,回调函数的参数为触发事件发送过来的数据,这里用 lambda形式busvm.$on('data-2',age=>{this.age=age;})}}}})</script>
结果:
假设页面加载完成就把第二个组件的数据发给第三个组件,使用钩子函数 mounted 挂载完成之后就发送,如:
mounted(){
busvm.$emit(‘data-2’,this.age);
}
——这样做是不行的。原因是第二个组件 mounted 挂载完成之后就发送,第三个组件 mounted 挂载完成之后去侦听,实际上这个时候,第三个组件还没开始去侦听哦,即是时机不对。正确的时机要是第三个组件开始侦听了,才能去发送数据的,否则接收不到数据。所以通过单击按钮肯定没问题的,因为当你单击按钮时,肯定所有的组件都挂载
完毕了,也就是第三个组件已开始侦听了