说到组件间通信,就要从props开始讲,组件虽然独立,但是组合成完整项目时还是需要组件间数据的传递,从而更好互动和联动实现复杂功能。
组件间通信
作为面试和笔试常考题,本身也是日常开发中必不可少的知识,这里的通信可以分为父子通信、隔代通信、以及全局通信。会涉及数据绑定,数据单向流动等问题。
props-父子间通信(单向)
props其实不是完整的父子间通信,通过props子代能够接收到父代想要传递的信息,反之不行,是数据是单向流动的。我认为是类似与函数传参的。
//父组件
.......
<HelloWorld msg="Welcome to Your Vue.js App" :text="text" />
.......
<script>
import HelloWorld from "./components/HelloWorld.vue";
......
export default {
name: "App",
data: function () {
return {
text: "父组件的data中的数据",
};
},
components: {
HelloWorld
},
......
</script>
//子组件
<template>
<div class="hello">
<p>-----------------子组件开始-------------</p>
<h1>{{ msg }}</h1>
<h2>子组件接收到“{{ text }}”</h2>
<p>-----------------子组件结束-------------</p>
</div>
</template>
<script>
//导出组件对象
export default {
name: "HelloWorld",
//props接收父组件传递的参数
props: ["msg", "text"]
};
</script>
父组件在使用子组件时通过给子组件标签添加自定义属性来传递信息,子组件通过props来获取传递来的信息,要求属性名字一一对应。
-
特点:子组件
props获取到的信息是单向的,通常当作data数据一样进行使用,不过不建议对里面的值进行修改,因为每次组件更新时都会刷新props里的值。并且项目编译会警告提示。计算属性可以操作props里面的值。父组件可以向子组件传递多个参数,子组件可以选择接收全部或部分参数,类似于js函数传参。
props多种格式
我们将这种props:["prop1","prop2"]称为简单声明,对应的还有详细声明,并且多种书写格式。详细声明下,如果不能满足类型限制就会报错,从而规范项目,让子组件开发更加可靠。
props: {
propA: Number, // 基础的类型检查 (`null` 匹配任何类型)
propB: [String, Number], // 多个可能的类型
propC: { type: String,
required: true // 必填的字符串
},
propD: { type: Number,
default: 100 // 带有默认值的数字
},
propE: { type: Object, // 带有默认值的对象或者数组填Array
default: function () { // 不建议直接填对象(因为对象直接量会一直占用内存),一般使用工厂函数,调用时才创建对象节省资源(面试)
return { message: 'hello' }
}
},
propF: {
validator: function (value) {// 自定义验证函数返回为true就代表数据符合我们规定
return ['success', 'warning', 'danger'].indexOf(value) !== -1
}
}
}
你会发现写法与ts有些相似,而且更加多元。
$emit-父子间通信(单向)
想要做到父子间通信有时候会选择props+emit是组件事件,因为组件标签不属于原生,所以没有默认事件,我可以设置自定义事件。
- 父组件在子组件上添加事件:
<Child :msg="msg" @xx="xx"/>
methods:{
xx(){
this.msg++
}
},
- 子组件内部去触发父组件设置在自己根组件上的事件。
<template>
<div id="child">
<button @click="change">改变</button>
<p>{{ msg }}</p>
</div>
</template>
<script>
export default {
props: ["msg"],
methods: {
change() {
this.$emit('xx');
},
},
};
</script>
v-model应用
v-model是v-bind和@input的语法糖。v-model可以用在子组件上。因为v-model中input事件函数已经写好,所以我们需要在子组件中触发即可。
//父组件
<Child @xx="xx" v-model="count"/>
{{count}}
v-model默认传参名是value,事件默认命名是input,都是指令底层写好的。
//子组件
props: ["value"],
methods: {
change() {
this.$emit('xx');
this.$emit('input',1+this.value)
},
},
- v-model在事件应用场景我们发现可以使用部分修饰符:
.trim.lazy.number:
//.trim用于清除value值中前后空格。
//.lazy将原本input事件转变为change事件,change事件是焦点离开+value值改变时触发。
//.number将value转变为number类型。
$emit方法第一个参数是事件名,第二个参数是给事件函数传入的参数。
事件原生化
通过事件修饰符.native,我们可以将非html原生标签上的事件变成原生事件。
//父组件
<Child @click.native="xx"/>
我们发现,通过.native我们将原生具有默认触发或处理的事件使用在组件标签上。否则就需要$emit进行触发。
修饰符sync
sync修饰符和v-model有类似的功能,等效写法:
// 加上sync之后父传子:
<com1 :a.sync="num"></com1>
// 它等价于
<com1 :a="num" @update:a="val=>num=val"></com1>
sync修饰符中默认事件名为update:变量名。
//子组件
methods: {
ff() {
this.$emit('update:a',this.a+1)
},
},
注意update:a是事件的全名。 一个标签上只能使用一个v-model指令,但是.sync修饰符不限次数。
$arrt
$emit实际上是vue原生上的方法,用于触发事件,this.$emit等于vm.$emit。
中央事件总线bus
$emit相当于我们通过js触发事件,而vue中我们通过$on完成事件绑定,$off负责解除事件绑定。
通过约定好的封装步骤,我们在原型上创建vue对象$bus,将所有自定事件通过$bus完成绑定、触发、执行。利于集中业务代码。
Vue.prototype.$bus = new Vue({
methods:{
on(eventname,cb){
this.$on(eventname,cb)
},
emit(eventname,data){
this.$emit(eventname,data)
},
off(eventname){
this.$off(eventname)
}
}
})
- 我们来用子组件进行测试
<template>
<div id="new">
<button @click="fn">创建</button>
<button @click="does">执行</button>
<button @click="destroy">销毁</button>
</div>
</template>
<script>
export default {
methods:{
fn(){
this.$bus.on('okk',(data)=>{
console.log(data)
})
},
does(){
this.$bus.$emit('okk','hahah')
},
destroy(){
this.$bus.$off('okk')
}
}
}
</script>
脱离于原生,node环节中提供了自定义事件的一套api: 绑定、触发、解绑事件。而vue环节下也同样提供了上述的自定义事件api。
$parent和$children
通过children访问父组件和子组件。
容易出现bug,因为隐蔽性太强,容易出现问题后查找麻烦。$children不能保证顺序,而且不是响应式。
编译和挂载顺序
created到beforeMount之间完成模板的编译,因为编译了父组件模板才会识别到子组件,所以编译模板顺序:祖->父->子->孙。beforeMount到Mount之间组件编译的模板要挂载到dom树上之前之前父组件dom需要先挂载子组件dom,所以挂载的顺序:祖<-父<-子<-孙。
ref
作为dom操作的api,是vue不建议的技术,为了处理某些特殊情况所以vue进行了执行。
Vuex-全局状态通信
Vuex是Vue全家桶的成员之一,用于全局状态保存。状态可以理解成变量。
$root
我们通过$root可以从组件下任何位置访问到访问到根组件。我们可以将$root作为Vuex一样创建全局变量使用,但是建议不用或则慎用!
console.log(this.$root)