目录
组件
组件的定义——实现应用中局部功能代码和资源的集合;
- 全局组件 Vue.component('标签名' , { }) 可以在多个vue实例中使用;
- 局部组件 components:{'标签名' : { } } 只能在在注册的vue实例中使用;
- 创建组件构造器; 传入一个配置对象; 这个对象除了el配置项,其他的都有;
- 注册组件; 两个参数, '组件标签' 和 构造器名字
- 使用组件; 在挂载点中使用;
<body> <div id="app"> <!--第三步 使用全局组件 --> <cpn></cpn> <!--第三步 使用局部组件 --> <cpn2></cpn2> </div> <script> // 第一步 创建组件的构造器 const cpnC = Vue.extend({ template: `<div> <p>你好</p> <h3>hello</h3> </div> ` }) //第二步 注册全局组件 Vue.component('cpn',cpnC) const vm = new Vue({ el:'#app', data:{}, methods:{}, // 第二步 注册局部组件 components:{ // 对象的key可以用引号,也可以不用 'cpn2':cpnC, // cpn2:cpnC } }); </script> </body>
注意:
- 调用Vue.extend()方法 - 创建组件构造器
- 调用Vue.component()方法 - 注册组件
- 在Vue实例的作用范围内 - 使用组件
- 组件构造器中的template中可以用引号(不能换行),用反引号(字符串模板可以换行);要有一个根标签<div>包裹;
- 在vue实例的配置项,components:{ } 注册局部组件 ;
- 对象的key值,可以加引号(当做字符串),也可以不加(变量)
- 对象字面量的增强写法: 函数名: funtion() 可以省略:function ;
- 对象的字面量增强写法: 对象的属性名和属性值一样,是个变量可以省略一个;
父子组件
子组件在父组件中注册,在父组件中使用;
在父组件的构造器对象的配置项: coponents:{ } 中注册子组件,;在template模板中使用组件;
<body> <div id="app"> <!-- 使用父组件B,包含子组件A --> <cpn-b></cpn-b> <hr> <!-- 在root根组件中注册组件A,直接使用子组件A --> <cpn-A></cpn-A> </div> <script> // 创建子组件构造器 cpnA const cpnA = Vue.extend({ template: ` <div> <h5> 我是子组件 </h5> </div> ` }) // 创建父组件构造器 cpnB const cpnB = Vue.extend({ template: // 在父组件的tempalte模板中使用子组件 ` <div> <h3> 我是父组件 </h3> <cpn-a></cpn-a> </div> `, // 在父组件的components中注册子组件 components:{ 'cpn-a':cpnA } }) const vm = new Vue({ el:'#app', data:{}, methods:{}, // 注册局部组件 父组件 components:{ 'cpn-b':cpnB, // 想直接单独使用子组件A,在根组件root组件中注册 'cpn-a':cpnA } }); </script> </body>
组件的语法糖注册方法
省略Vue.extend()书写,直接把配置对象{ }写到注册组件中去(替换了构造器名字);
构造器名字,直接写成一个对象;这个对象就是之前构造器传入的配置对象;
但Vue会仍会自动的将此对象给Vue.extend()
// 内部会自动调用Vue.extend()
<body> <div id="app"> <!-- 使用全局组件 --> <cpn-a></cpn-a> <hr> <!-- 使用局部组件 --> <cpn-b></cpn-b> </div> <script> // 注册全局组件 Vue.component("cpn-a",{ template:'<div><h4>我是全局组件</h4></div>' }) const vm = new Vue({ el:'#app', data:{}, methods:{}, // 注册局部组件 components:{ "cpn-b":{ template:` <div> <p>我是局部组件</p> </div> ` } } }); </script> </body>
组件构造器的配置对象中的template模板的抽离写法:
template模板写: '选择器';
template便签内写: 选择器; 需要有个根标签<div>包裹;<div>内写需要的模板;
template标签不会被渲染,
<body> <div id="app"> <!-- 使用组件 --> <cpn></cpn> </div> <!-- template模板的抽离写法 --> <template id="aaa"> // 选择器 <div> <h3>模板的分离写法</h3> </div> </template> <script> // 注册组件 Vue.component('cpn',{ template:'#aaa' //选择器 }) const vm = new Vue({ el:'#app', data:{}, methods:{} }); </script> </body>
组件使用数据
子组件不能直接使用父组件中的数据;
vue实例也是一个父组件,root根组件;
组件可以使用自己组件内的data配置项中的数据;
组件的data属性,必须是一个函数,且返回一个对象;
组件也有自己的data属性(在vue.extend()的配置对象中),必须是一个函数,且返回一个对象;
<body> <div id="app"> <!-- 使用组件 --> <cpn></cpn> </div> <template id="aaa"> <div> <!-- 使用组件data中的数据 --> <h3>{{msg}}</h3> </div> </template> <script> // 注册组件 Vue.component('cpn', { template: '#aaa', // 必须是一个函数,且返回一个对象 // 对象的字面量增强写法 省略 :function data() { return { msg: '组件的data必须是一个函数' } }, // data:function () { // return{ // msg:"组件的data必须是一个函数,且返回一个对象" // } // } }) const vm = new Vue({ el: '#app', data: {}, methods: {} }); </script> </body>
父子通信
通过props实现父子通信;
把父组件的数据传递给子组件;子组件来使用;
父组件通过props向子组件传递数据 [props=> property 属性]
方法一: props:[ ]
- 在子组件的配置对象中的添加props配置项,是一个数组;
- 数组中的字符串是变量, 用来接受父组件传递过来的数组;
- 使用子组件时,用v-bind属性绑定, 用props中的变量接受父组件中的数据;
- 在子组件的模板中使用这些props[ ]中的变量;
<body> <div id="app"> <!-- 使用子组件,父组件是vue实例 --> <!-- 通过v-bind绑定props中的变量; 接收父组件传递给子组件的数据 --> <cpn :cmsg="msg" :cmovies="movies"></cpn> </div> <template id="aaa"> <div> <!-- 使用父组件传递过来的数据 --> <h2>{{cmsg}}</h2> <ul> <!-- 使用父组件传递过来的数据 --> <li v-for="item in cmovies">{{item}}</li> </ul> </div> </template> <script> const vm = new Vue({ el: '#app', data: { msg: '你很棒', movies: ['西游记', '三国演义', '红楼梦', '水浒传'] }, methods: {}, // 注册局部组件 components: { // 组件中的对象的key(属性名) 就是组件标签名, 可以加引号,也可以不加 'cpn': { template: '#aaa', // 数组中的字符串就是变量, props: ['cmsg','cmovies'], data() { return { } } } } }); </script> </body>
方法二: props:{ }
- props是一个对象时, 可以对props中的变量(key)进行类型限制;
- 这些变量是用来接受父组件传递过来的数据的;
- 这些变量是key,可以加引号,也可以不加;
- 这些变量对应的value可以是一个对象,对这个变量进行限制;
- required: true 表示这个变量必传;
- default 是当没有数据接收时,显示默认值;
- 当数据类型是数组或对象时, 默认值必须是一个函数,且return 默认值;
props: { cmsg:String, //字符串类型 cbooks: { type:[ Array, Object ]// 数组或对象类型 // 当数据类型是对象或数组时, default必须是一个函数,且返回默认值 //default是没有接受数据时,才会显示默认值,与required一起添加到同一个对象上有点冲突; default() { return ['大闹天宫'] //return { } }, // 必须要传的值 required: true } }
props的数据类型验证:
props: { // 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证) propA: Number,// 要求这个变量的数据类型是Number; // 多个可能的类型 propB: [String, Number],//数据类型是字符串或数字类型; // 必填的字符串 propC: { type: String, // 数据类型是字符串类型,且这个变量必须使用; required: true }, // 带有默认值的数字 propD: { type: Number, default: 100 // 默认值,当没有接受数据时,默认值显示 }, // 带有默认值的对象或数组 propE: { type:[Object,Array], // 当数据类型是对象或数组时, 默认值必须函数获取且 return 默认值; default: function () { return { message: 'hello' } //return [ ] } }, // 自定义验证函数 propF: { validator: function (value) { // 这个值必须匹配下列字符串中的一个 return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } }
props的大小写
- camelCase (驼峰命名法) myName;
- kebab-case (短横线分隔命名) my-name;
- 在props里 接收数据的变量定义时, 用驼峰命名;
- 但接收父组件的数据时(v-bin:) 要用短横线分割法命名;
- 在template模板中,使用变量时 用驼峰命名
<body> <div id="app"> <!-- v-bind: props的变量名要用短横线分割法,来接收父组件的数据 --> <cpn v-bind:my-first-name='name'></cpn> </div> <template id="aaa"> <div> <!-- 使用变量接收来的数据 这里使用驼峰名 --> <h2>{{myFirstName}}</h2> </div> </template> <script> Vue.component('cpn',{ template:"#aaa", props:{ // 在props里 接收数据的变量定义时, 用驼峰命名; // 但接收父组件的数据时(v-bin:)要用短横线分割法命名; // 在template模板中,使用变量时 用驼峰命名; myFirstName:{ // 对象的key也可以加引号;'myFirstName' type:String, // default:'牛B', required:true } } }) // vue的实例 => root根组件 const vm = new Vue({ el:'#app', data:{ name:'Liu' }, methods:{} }); </script> </body>
子父通信
通过自定义事件,进行子父通信;
把子组件中的数据传递给父组件,父组件来使用;
- 在子组件的methods{ }中,子组件的事件处理函数中,通过 this.$emit('自定义事件' ,参数 ) 把自定义事件发射出去;
- 在父组件的标签中,接收这个自定义事件,并通过v-on进行事件监听这个自定义事件, 把自定义事件 赋值给父组件,让父组件来处理这个自定义事件;
- 在父组件的methods{ } 中, 处理父组件的处理函数;
- 子组件传递过去的参数,默认父组件的处理函数可以直接使用;
<body> <div id="app"> <!-- 使用子组件 --> <!-- 父组件中接受这个自定义事件,并通过v-on事件监听,把子组件发射出来的自定义事件给父组件,让父组件来处理 --> <!-- 默认会把子组件发射出来的参数赋值给父组件 --> <cpn @itemclick='fclick'></cpn> </div> <template id="aaa"> <div> <!-- 子组件自己的事件处理函数,item参数就是数组中的每一项 --> <button v-for="item in fenlei" @click='btnClick(item)'> {{item.name}} </button> </div> </template> <script> const vm = new Vue({ el:'#app', data:{ }, // 父组件的方法 methods:{ // 这个参数,是子组件发射出来的自定义事件传递过来的参数 fclick(item){ console.log('子父通信',item); } }, // 注册子组件 components:{ cpn:{ template:'#aaa', data() { return { fenlei:[ {id:1,name:'西游记'}, {id:2,name:'水浒传'}, {id:3,name:'三国演义'}, {id:4,name:'红楼梦'} ] } }, // 子组件自己的方法,里面写事件处理函数 methods: { btnClick(item){ // console.log(item); //子组件自己的item // 在子组件的事件中,把自定义的事件发射给父组件 // 两个参数,'自定义事件名',参数 (把参数传出去,默认的传到父组件去) // 这个item是子组件自己的数据 this.$emit('itemclick',item) }, }, } } }); </script> </body>
父组件访问子组件
父组件可以直接访问子组件的数据,调用子组件的方法等;
- $refs ( 子组件中 ref='标记') =>访问指定的子组件
- $children => 访问所有子组件
- 父组件通过$children访问子组件;
- 会得到一个数组,通过数组的下标访问,不推荐这种方式,因为可能插入新的组件,导致下标错位;
- vue组件数组 [VueComponent]
<body> <div id="app"> <cpn></cpn> <cpn2></cpn2> <button @click=fbtnClick>点击我</button> </div> <!-- 第一个组件的模板 --> <template id="aaa"> <div> <h4>第一个子组件</h4> </div> </template> <template id="bbb"> <div> <h4>第二个子组件</h4> </div> </template> <script> const vm = new Vue({ el:'#app', data:{}, methods:{ fbtnClick(){ console.log(this.$children);//vue组件数组 [VueComponent] this.$children[0].btnClick() // 调用第一个子组件的里的方法 console.log(this.$children[0].msg); // 打印第一个子组件的数据 this.$children[1].btnClick2() // 调用第二个子组件的里的方法 console.log(this.$children[1].msg);// 打印第二个子组件的数据 } }, components:{ // 第一个子组件 'cpn':{ template:"#aaa", data() { return { msg:'第一个子组件的msg数据' } }, methods: { btnClick(){ console.log("第一个子组件的方法"); } }, }, // 第二个子组件 'cpn2':{ template:"#bbb", data() { return { msg:'第二个子组件的msg数据' } }, methods: { btnClick2(){ console.log("第二个子组件的方法"); } }, }, } }); </script> </body>
- 父组件通过$refs访问子组件 (推荐)
- 得到的, 默认是一个空的对象
- 需要在组件上添加 ref='标记',这个属性; 这个对象不为空,'标记'作为对象的key;
- {a: VueComponent}
- 这个'标记'会作为对象的key,通过点语法来访问,得到value;
- 父组件通过这个方法访子组件更准确,拿到指定的子组件;
<body> <div id="app"> <!-- $refs访问子组件 ; 要给子组件添加 ref='标记' 这个属性; 这个'标记'作为对象的key, 通过点语法 获取对应的value值; 通过 this.$refs.'标记'. 来访问指定的子组件 --> <cpn ref="a"></cpn> <button @click=fclick>点击我</button> </div> <template id="aaa"> <div> <h4>{{msg}}</h4> </div> </template> <script> const vm = new Vue({ el: '#app', data: {}, methods: { fclick(){ console.log(this.$refs); // 不给子组件添加ref='标记'属性,默认是一个空对象 {}, // 加了这个属性之后,就会添加 {a: VueComponent},这个'标记'作为key // 子组件指定的'标记'作为这个对象的key, 通过点语法 来获取对应组件的value; console.log(this.$refs.a.msg); // 打印指定子组件的data中的数据msg this.$refs.a.btnClick() //调用指定子组件的方法 } }, components: { 'cpn': { template:"#aaa", data() { return { msg:'我是子组件msg' } }, methods: { btnClick(){ console.log('子组件的方法'); } }, } } }); </script> </body>
子组件访问父组件/访问根组件
子组件可以访问父组件,直接使用父组件的数据,或调用父组件的方法
- this.$parent 访问上一级父组件
- this.$root 访问根组件
<body> <div id="app"> <!-- 在父组件中使用子组件 son --> <son></son> </div> <!-- --> <!-- 子组件模板 son --> <template id="son"> <div> <h2>我是子组件</h2> <button @click='sonClick'>子组件按钮</button> <!-- 在子组件中使用孙组件 --> <sun></sun> </div> </template> <!-- --> <!-- 孙组件模板 sun --> <template id="sun"> <div> <h4>我是孙组件</h4> <button @click='sunClick'>孙组件按钮</button> </div> </template> <!-- --> <script> // 根组件(vue实例) const vm = new Vue({ el: '#app', data: { 'rootMsg': "我是根组件(vue实例)的数据---rootMsg" }, methods: { rootClick() { console.log('我是根组件的事件---rootClick'); } }, // // 注册子组件 son components: { 'son': { template: '#son', data() { return { sonMsg: '我是子组件的数据-----sonMsg' } }, methods: { sonClick() { console.log('我是子组件的事件----sonClick'); console.log('子组件的this.$parent==>',this.$parent); //打印this.$parent console.log('子组件的this.$root==>',this.$root); //打印根组件this.$root(vue的实例) console.log('子组件的this.$parent==>',this.$parent.rootMsg); // 打印父组件的data中的rootMsg this.$parent.rootClick() // 调用父组件的rootClick()方法 // 同时父组件的methods配置项中也处理着其他业务; console.log('-------------分割线--------------'); } }, // // 注册孙组件 components: { 'sun': { template: '#sun', data() { return { 'sunMsg': '我是孙组件的数据-----sunMsg' } }, methods: { sunClick() { console.log('我是孙组件的事件----sunClick'); console.log('孙组件的this.$parent==>',this.$parent); //打印this.$parent console.log('孙组件的this.$root==>',this.$root); //打印根组件this.$root(vue的实例) console.log('孙组件的this.$parent==>',this.$parent.sonMsg); // 打印父组件的data中的rootMsg this.$parent.sonClick() // 调用父组件的sonClick()方法 console.log('-------------分割线--------------'); } }, } } } } }); </script> </body>
插槽
- 具名插槽
- 匿名插槽
- 作用域插槽
- 预留插槽就是预留一个空间,插槽内放东西,让组件具有更强的扩展性;
- 插槽写在组件的template模板中;
- <slot>的位置即组件标签的位置;
- 插槽就是 把组件标签内容的显示;
- 插槽的三种情况如下:
- 1.组件标签内无内容, <slot>内放内容, 显示<slot>里的内容,插槽的默认值;
- 2.组件标签内有内容,<slot>内无内容,显示组件标签的内容;
- 3.组件标签内和<slot>内都有那内容, 组件里的内容会替换<slot>里的内容;
- 3.组件标签内的内容会作为一个整体 替换插槽内所有的内容
<div id="app"> <!-- 使用组件 --> <cpn> <p>你好</p> <p>hello</p> </cpn> <hr /> <cpn></cpn> </div> <!-- 子组件的template模板 --> <template id="aaa"> <div> <!-- 插槽 --> <slot> <p>AAA</p> </slot> <h4>匿名插槽</h4> <!-- --> <slot>默认值</slot> </div> </template>
具名插槽
具名插槽 : 有名字的插槽;
<span slot="aaa"></span> <slot name="aaa"></slot>
- 组件标签内的 元素添加slot属性
- 插槽slot标签 添加 name 属性
- 这两个属性值相同,即可更改对应的内容;
- 组件标签内的内容会作为一个整体 替换插槽内所有的内容
<div id="app"> <cpn> <button slot="center">Center</button> </cpn> <!-- <cpn></cpn> --> </div> <template id="aaa"> <div> <slot name='left'><span>左边</span></slot> <slot name='center'><span>中间</span></slot> <slot name='right'><span>右边</span></slot> </div> </template>
编译作用域
父组件模板的所有东西都会在父级作用域内编译;
子组件模板的所有东西都会在子级作用域内编译。
作用域插槽
父组件替换插槽的标签,但是内容由子组件来提供。
- 在子组件模板的使用插槽, 这个插槽通过v-bind属性绑定, 把子组件的数据赋值给标签B; v-bind:bbb="books"
- 使用子组件标签时,使用<template>包裹内容,<template>添加slot-scope属性, slot-scope = '标记A';
- 通过 标记A.标记B 使用子组件的数据
<body> <h2>作用域插槽: 父组件替换插槽的标签,但是内容由子组件来提供。</h2> <div id="app"> <!-- 使用第一个子组件 --> <cpn></cpn> <!-- 使用第二个子组件 --> <cpn> <!-- 在使用组件时,子组件标签的内部写template标签 添加固定的 slot-scope 属性 --> <!-- slot-scope = "标记aaa" --> <template slot-scope="aaa"> <div> <!-- 通过点语法 使用子组件的传过来的数据bbb --> <div>{{aaa.bbb.join("***")}}</div> <span v-for="item in aaa.bbb">{{item}}***</span> </div> </template> </cpn> </div> <template id="aaa"> <!-- 作用域编译,子组件模板的所有东西都会在子级作用域内编译 --> <div> <!--在slot插槽上, 把子组件的数据books通过v-bind属性绑定 赋值给 标记 bbb --> <slot v-bind:bbb="books"> <span v-for="item in books">{{item}}---</span> </slot> </div> </template> <script> const vm = new Vue({ el: "#app", data: {}, methods: {}, methods: {}, components: { cpn: { template: "#aaa", data() { return { books: ["西游记", "水浒传", "红楼梦", "三国演义"], }; }, }, }, }); </script> </body>