组件的特点
- 可复用
- 方便维护
- 每一个vue组件都是一个独立的个体(独立的vm实例);DATA是独立的(不同组件的data互不干扰)、有完整的生命周期、方法都是独立的
- 能够实现组件的嵌套;需要掌握组件之间的信息通信
全局组件
无需单独引用或者配置,直接在大组件中调取全局组件即可 ```javascript Vue.component(组件名,options)
options可以使用的有vm实例具备的大部分(data,methods,生命周期函数…)
每调用一个组件都是创建一个单独的vue实例(VueComponent->Vue)
<a name="7lS22"></a>## 组件的命名规则- kebab-case:短横线作为分隔符,只能基于kebab方法调取- PasalCase:单词首字母大写,也是基于kebab方式调取(如果在template模板中可以使用Pasal方式调取)- 调取组件的时候,会把所有组件的单词渲染为小写字母(命名除了PaselCase模式外,都要把组件名设置为小写,调取组件的时候可以是大写也可以是小写,最后都是按小写渲染的)- 命名的时候尽量不要出现其余的特殊字符```html<!DOCTYPE html><html><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="ie=edge"><meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0"><title></title></head><body><div id="app"><h3 v-text='title'></h3><!--调取组件+ 单闭合:不符合w3c规范,调取完成后,后面的视图不识别(避免使用)+ 双闭合:可以设置除组件规定内容外的其余内容(slot插槽)--><my-button/><my-button>我加的其它内容(slot 插槽)</my-button></div><!-- IMPORT JS --><script src="./node_modules/vue/dist/vue.js"></script><script>//全局组件Vue.component('MyButton', {template: `<button>组件中的按钮</button>`});let vm = new Vue({el: '#app',data: {title: '我很开心'}});</script></body></html>
局部组件
父子通信
子组件通过props接受父组件的数据,子组件通过$emit触发父组件的自定义事件
父组件传递数据给子组件
父组件传递数据给子组件实质: 是通过行内属性的方式传递给子组件
父传子: 让子组件使用父组件的数据
1、$parent 通过获取组件的方式
2、自定义属性 + props (type default required validator)
3、$attrs 可以获取没有被props接收的剩余的属性
4、provide/inject
5、$children + 索引
6、$refs
—— $root
1. props
prop的大小写
在dom中的模块,驼峰命名的prop名要使用短横线分割命名替换,但是如果是字符串模板,则没有此限制
prop的类型
/* props: {title: String,likes: Number,isPublished: Boolean,commentIds: Array,author: Object,callback: Function,contactsPromise: Promise // or any other constructor}*/function a(id){this.id=id}Vue.component('blog-post', {props: {author: a}})
prop的验证
//type类型可以是string,number,boolean,array,object,data,function,symbol//type 还可以是一个自定义的构造函数,并且通过 instanceof 来进行检查确认Vue.component('my-component', {props: {// 基础的类型检查 (`null` 和 `undefined` 会通过任何类型验证)propA: Number,// 多个可能的类型propB: [String, Number],// 必填的字符串propC: {type: String,required: true},// 带有默认值的数字propD: {type: Number,default: 100},// 带有默认值的对象propE: {type: Object,// 对象或数组默认值必须从一个工厂函数获取default: function () {return { message: 'hello' }}},// 自定义验证函数propF: {validator: function (value) {// validator 是自定义的校验函数// val 就是这个 prop 收到的值// 自定义校验规则,如果校验通过return true,否则抛出异常或者return false// 这个值必须匹配下列字符串中的一个return ['success', 'warning', 'danger'].indexOf(value) !== -1}}}})
注意点:prop 会在一个组件实例创建之前进行验证,实例的属性 (如 data、computed 等) 在 default 或 validator 函数中是不可用的。
prop静态传值或者动态传值
prop是父组件传递数据的自定义属性。
- prop 传递静态的,直接传一个值
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"></head><body><!-- 父组件 --><div id="app"><son sonvalue3="'abcd'" sonvalue="f" sonvalue2="33"></son></div><!-- 子组件,此处静态传数据时,必须是确定值 --><template id="son"><div><h2>我是子组件</h2>引用父组件中的数据项:{{sonvalue}}- {{sonvalue2}}- {{sonvalue3}}</div></template><script src="https://cdn.jsdelivr.net/npm/vue"></script><script type="text/javascript">//1.定义组件对象let Son = {template: "#son",props: {sonvalue: {type: Number},sonvalue2: {type: Number,default: 23},sonvalue3: {type: String,required: true}}}let vm = new Vue({el: "#app",components: { //2.注册组件对象Son}});</script></body></html>
- prop使用v-bind,在传入数字,布尔值,数组,对象时,即使是静态的,也必须使用v-bind绑定,因为此时他是表达式不是字符串;当将一个对象所有的属性作为prop传入时,可以使用不带参数的,用一个对象包含所有属性,然后把对象名绑定在元素上即可。
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"></head><body><!--父组件把信息传递给子组件:props属性传递- 默认传递给子组件的属性值都是字符串格式的,如果需要传递数据的格式是数据本身应该具备的格式,我们需要基于v-bind实现传递(哪怕传递的属性值还是固定)- 可以把子组件当做一个标签,我们可以设置ID/CLASS/STYLE等内置属性值,这些属性也会传递给子组件,VUE帮我们处理好的,该合并的合并,该覆盖的覆盖,无需我们在PROPS中注册处理--><!-- 父组件 --><div id="app"><!-- 子组件 --><son :sonvalue3="Parentvalue3" :sonvalue="Parentvalue" :sonvalue2="Parentvalue2"></son></div><template id="son"><div><h2>我是子组件</h2>引用父组件中的数据项:{{sonvalue}}- {{sonvalue2}}- {{sonvalue3}}</div></template><script src="https://cdn.jsdelivr.net/npm/vue"></script><script type="text/javascript">//1.定义组件对象let Son = {template: "#son",props: {sonvalue: {type: Number},sonvalue2: {type: Number},sonvalue3: {type: String}}}let vm = new Vue({el: "#app",data:{Parentvalue:'12',Parentvalue2:'90',Parentvalue3:"'asss'"},components: { //2.注册组件对象Son}});</script></body></html>
单项数据流prop注意点
- prop是父子之间形成的单行向下传递数据,禁止逆向传递。
- 当父组件发生更新时,子组件所有的prop会变成新的值。因此不应该在子组件内部改变prop,会造成错误。
重点
- 情景一:prop传递初始值时,子组件要把他作为一个本地的prop数据使用。
// 解决办法:定义一个本地的data属性并将这个prop用作初始值。props: ['initialCounter'],data: function () {return {counter: this.initialCounter}}
- 情景二:prop以一种原始的值传入并需要进行转换。
//解决办法:使用这个prop的值定义一个计算属性props: ['size'],computed: {normalizedSize: function () {return this.size.trim().toLowerCase()}}//在 JavaScript 中对象和数组是通过引用传入的,所以对于一个数组或对象类型的 prop 来说,在子组件中改变这个对象或数组本身将会影响到父组件的状态。
2. $parent
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"></head><body><div id="app"><h1>{{name}}</h1><father></father></div></body><template id="father"><div><div @click="facount++">父组件:{{facount}}</div><son :q='facount'></son></div></template><template id='son'><div @click.stop='fn'>子组件:{{$parent.facount}}<h2>接受父组件的数据:{{q}}</h2></div></template><script src="https://cdn.jsdelivr.net/npm/vue"></script><script type="text/javascript">// 父传子 就是在子组件使用的标签上 添加行内属性// 自组件中 通过props属性接收传进来的值;// 这个props对应的属性的属性值只能看 不能改;Vue.component('father',{template:'#father',data() {return {facount:100}},})Vue.component('son',{template:'#son',props:['q'],data() {return {}},methods: {fn(){//this.$parent 可以获取整个父组件,//那么整个父组件中的属性或者方法 我们可以随意调用//this.$parent.facount+=10;console.log(this.q)// 从父组件接手过来的数据 我们不能直接修改// 因为这么修改 有被重写的风险// 每当父组件更新一下, 传进来的数据就会被重写this.q= 1000;}},})let vm = new Vue({el:'#app',data:{name:"珠峰"},});
子组件向父组件传递
子传父: 通过让子组件告诉父组件要执行什么代码
1、$parent
2、自定义事件 + $emit : 所有再组件上边的事件都是自定义事件
3、$listeners 获取传给子组件的所有的自定义事件
4、provide/inject provide(){return {name:this.name}}
1. 利用emit
事件名
在向父组件传递时,触发的事件名必须匹配监听事件所用的名称,事件名没有自动化的大小写转换,v-on事件监听器在dom模块中会被自动转换为全小写,会造成事件名不相同造成问题,因此建议使用短横线分割。
思路
在子组件里$emit绑定一个事件($emit(“事件名”,”参数”)),执行emit时,会把参数传递给父组件,父组件通过v-on监听并接收参数
<!DOCTYPE html><html><head><meta charset="UTF-8"><title></title></head><body><div id="app"><h2>下面的数据项是从根组件中取到的</h2><ol><li v-for="item in todo">{{item}}</li></ol><hr />这是一个子组件,我们希望把子组件中的数据传递给父组件<!-- 第三步:父组件接受子组件的方法 --><!--<子组件 @子组件发回事件名="父组件要执行的方法"></子组件>--><son @submitmsg="addmsg"></son></div><template id="son"><div><input type="text" v-model="txt" /><!-- 第一步:子组件绑定事件 --><button @click="add">添加</button><p> 在子组件中的数据如下:</p><p>{{txt}}</p></div></template><script src="https://cdn.jsdelivr.net/npm/vue"></script><script type="text/javascript">//1.定义组件对象,子组件let Son = {template: "#son",data: function () {return {txt: ""}},methods: {add: function () {console.info(this.txt);//第二步:子组件向父组件发送一个方submitmsg//emit内容少的时候,可以直接放到@click后面,用值直接写就可以this.$emit("submitmsg", {msg: this.txt,t: new Date()})}}}let vm = new Vue({el: "#app",data: {todo: ["天气变冷了","注意不要感冒了"]},components: { //2.注册组件对象Son},methods: {// 第四步:接受子组件方法之后,执行父组件方法addmsg: function (info) {console.info("父组件收到了子组件发布的事件", info);this.todo.push(info.msg);}}});</script></body></html>
2. v-model
一个组件上的v-model默认会利用名为value的prop和名为input的事件。使用父组件v-model传值,子组件props[‘value’]接收而子组件也可以通过$emit(‘input’,false),去改变父组件中v-model 和 子组件中 value 的值 。
**
使用v-model来进行双向数据绑定的时:
<input v-model="something">
仅仅是一个语法糖:
<input v-bind:value="something" v-on:input="something = $event.target.value">
所以在组件中使用的时候,相当于下面的简写:
<custom v-bind:value="something" v-on:input="something = $event.target.value"></custom>
所以要组件的v-model生效,它必须:
- 将value绑定到名为value的prop上
- 在其input事件被触发时,将新的值通过自定义的input事件触发
父组件
<template><div class="toggleClassWrap"><!-- 自定义v-model="isShow"应用 此处将isShow 传递给子组件中的props --><modelVue v-if="ifShow" v-model="ifShow"></modelVue></div></template><script type="text/javascript">import modelVue from '../../components/model.vue'export default{data () {return {ifShow:true,}},components : {modelVue}}</script>
子组件
<template><div id="showAlert"><div>showAlert 内容</div><button class="close" @click="close">关闭</button></div></template><script>export default{//1.将value绑定到名为value的prop上props:{value:{type:Boolean,default:false,}},data(){return{}},methods:{close(){//2.将value绑定到名为value的prop上this.$emit('input',false);//传值给父组件, 让父组件监听到这个变化}},}</script><style scoped>.close{background:red;color:white;}</style>
3. .sync
可以通过prop进行双向绑定,同时配合this.$emit(update:myPropName,newValue)触发事件。.sync相当一个语法糖,会被扩展为自动更新父组件属性的v-on监听器,(即让自己手动更新父组件的值,进而使数据来源更改更明显)
//作为语法糖的原本写法<text-documentv-bind:title="doc.title"v-on:update:title="doc.title = $event"></text-document>//利用.sync语法糖简写的形式<text-document v-bind:title.sync="doc.title"></text-document>
父组件
<template><div class="vuexWrap common"><!--使用sync修饰符可以简化子组件向父组件传递数据的过程;--><!--1. 在父组件使用子组件时,prop后面跟一个.sync ,然后取消显式声明的事件监听--><!--2. 子组件触发事件:this.$emit('update:prop名字', 新数据)--><childrenOne :title.sync="doc.title"></childrenOne></div></div></template><script type="text/javascript">import childrenOne from '../../components/childrenOne.vue'export default{data () {return {doc:{title:'index'},}},mounted (){//childrenOnealert(this.doc.title);},components : {childrenOne}}</script>
子组件
<template><div class="OneWrap common">{{title}}</div></template><script type="text/javascript">export default{props:{title:""},data () {return {newTitle:"childrenOne"}},mounted (){this.$emit('update:title', this.newTitle);},}</script>
4. ref
- ref加在原生的DOM元素上,通过ref获取的是原生的DOM对象(基于ref把当前元素放置到this.$refs对象,实现对dom直接操作);如果加在组件上,获取的是这个组件实例的一个引用;拿到这个实例后可以访问上面的数据、调用组件的方法
- 只有在mounted及之后才能获取到
- $parent和$children是获取组件和子组件的实例,只不过$children是一个数组集合,需要记住组件顺序才可以
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><title>Title</title></head><body><div id="app" :style="{color: xColor}"><p ref="p1">{{msg}}</p><ul><!-- <li v-for="(item, index) in ary"v-if="index % 2 != 0"ref="listItem":key="index">{{item}}</li>--><li v-for="(item, index) in oddIndex"ref="listItem":key="index">{{item}}</li></ul></div><script src="vue.js"></script><script>// Vue是数据驱动的,不提倡操作DOM;但是必要的时候还是需要操作DOM的;Vue提供了专门的方式获取DOM;// ref属性 和 vm.$refs// 如果这个列表不需要全部渲染,可以写一个计算属性,v-for 这个计算属性// 或者,v-if 条件渲染let vm = new Vue({el: '#app',data: {msg: 'hello',ary: [1, 2, 3, 4],xColor: ''},computed: {oddIndex() {return this.ary.filter((item, index) => index % 2 !== 0);}},mounted() {// console.log(this.$refs);console.log(this.$refs.p1);console.log(this.$refs.listItem);// 我们通过this.$refs获取DOM元素;this.$refs.p1.style.color = 'red'; // 可以实现,但是不推荐操作DOM;// 首先要在获取的元素添加 ref="标识符" 的行内属性// 获取的时候this.$refs.标识符 获取元素;// 如果相同的ref有一个,获取到的就是带这个ref的原生元素对象// 如果相同的ref有多个,获取到的是所有带有这个ref的元素组成的数组//基于REF可以把当前元素放置到this.$refs对象中,从而实现对DOM的直接操作(只有在mounted及之后才可以获取到)}})</html>
获取子组件的实例
- this.$children[0].flag ->需要知道是数组的哪一个
给子组件加ref,this.$refs.实例,
ref用于获取组件
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title></head><body><div id="app"><h1>{{name}}</h1><ul><li ref='www'></li><li ref='www'></li><li ref='www'></li></ul><ul><li v-for='item in ary' ref='qqq'></li></ul><my-btn v-for='item in ary' :key='item' ref='qqq'></my-btn></div></body></html><template id='btn'><button @click='clickFn' class='aaa' >按钮</button></template><script src="../node_modules/vue/dist/vue.js"></script><script>// ref除了获取元素 还可以获取 组件;// 获取到组件之后 我们就可以根据自己的需求进行编写// 父组件怎么调用到 子组件的 methods中的函数// 子组件怎么调用到 父组件的 methods中的函数let obj = {template:'#btn',methods: {clickFn(e){this.$emit('click',e);console.log(this)}},}let vm = new Vue({el:'#app',data:{name:"珠峰",ary:[1,2,3,4]},mounted() {// ref获取元素 在多个元素的时候 只能获取一个// 若是通过 v-for循环出来的 就都可以获取到// DOM的更新是一个异步操作console.log(this.$refs.qqq)// v-for出来的可以获取一组console.log(this.$refs.www)// 静态写死的只获取一个this.ary.pop();this.$nextTick(()=>{// DOM更新完成之后才会触发; DOM的更新是异步的console.log(this.$refs.qqq)})},components:{'my-btn':obj},});</script>
5. $parent和$child
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"></head><body><div id="app"><h1>{{name}}</h1><father></father></div></body><template id="father"><div><!-- 只要组件上的事件 不管长成什么样 都是自定义事件 --><div @click="facount++">父组件:{{facount}}</div><son :q='facount'></son></div></template><template id='son'><div @click.stop='fn'>子组件:{{$parent.facount}}<h2>接受父组件的数据:{{q}}</h2></div></template><script src="https://cdn.jsdelivr.net/npm/vue"></script><script type="text/javascript">// 父传子 就是在子组件使用的标签上 添加行内属性// 自组件中 通过props属性接收传进来的值;// 这个props对应的属性的属性值只能看 不能改;Vue.component('father',{template:'#father',data() {return {facount:100}},})Vue.component('son',{template:'#son',props:['q'],data() {return {}},methods: {fn(){//this.$parent 可以获取整个父组件,//那么整个父组件中的属性或者方法 我们可以随意调用//this.$parent.facount+=10;console.log(this.q)// 从父组件接手过来的数据 我们不能直接修改// 因为这么修改 有被重写的风险// 每当父组件更新一下, 传进来的数据就会被重写this.q= 1000;}},})let vm = new Vue({el:'#app',data:{name:"珠峰"},});
6. $attr
可以获取没有被props接收的那些参数
<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Document</title><link rel="stylesheet" href="../node_modules/element-ui/lib/theme-chalk/index.css"></head><body><div id="app"><!-- 只要组件上的事件 不管长成什么样 都是自定义事件 --><h1>{{name}}</h1><mycount :count='count' @add123='fn' @minus123='fn'></mycount><el-button type="primary" icon="el-icon-edit" @click='fn2' class='qqq'></el-button><my-button @click='fn2' class='qwer'></my-button></div></body></html><template id='mycount'><div><h2>数字是{{count}}</h2><button @click='add'>增加</button><button @click='minus'>减少</button></div></template><script src="../node_modules/vue/dist/vue.js"></script><script src="../node_modules/element-ui/lib/index.js"></script><script>// 子传父 让父组件使用子组件的数据;也就是子组件可以修改父组件的数据/*1、$parent2、自定义事件 + $emit(官推)3、$listeners 可以接收所有自定义事件 this.$listeners.事件名(参数)4、provide/inject// 2 3 4这三种方法 都是一个套路: 把父组件的函数 传给子组件,然后再子组件中执行对应的函数,并通过参数的方式 把子组件的数据给父组件*/let mycount = {template:'#mycount',props:['count'],// props 优先于 datacreated() {console.log(this)},methods: {add() {// this.$parent.count++// this.$emit('add123',1,2,3,4,5,6,7)this.$listeners.add123(1,2,3)},minus(){// this.$parent.count--// this.$emit('minus123',100,200,300,400)this.qqq(100,200)}},inject:['qqq']}let vm = new Vue({el:'#app',data:{name:"珠峰",count:0},components:{mycount},methods: {fn(n){console.log(arguments)this.count += n},fn2(){console.log(arguments)}},provide(){return {qqq:this.fn}}});</script>
隔代通信
- provide和inject
{provide:{//对象或者返回对象的函数都可以(属性如果是data中的数据,则必须使用函数的方法进行处理)name:"aaa";}}//后代组件基于inject声明需要使用的数据并调取使用{inject:['name'],methods:{function(){let name=this.name;}}}
