Vue组件父子组件通讯
设计的面试题
Vue中父子组件通信有哪些方式?
概述
通信方式无外乎就那几种:
Prop 常用
$emit 组件封装用的较多
.sync 语法糖 (较少)
$attrs和$listeners(组件封装用的较多)
provide和inject(高阶组件/组件库用的较多)
其他方式通信
详情
1 Prop
我们用的最多方式,可以通过Prop向子组件传递数据。
用一个形象的比喻来说,父子组件之间的数据传递相当于自上而下的下水管子,只能从上往下流,不能逆流。这也正是Vue的设计理念之单向数据流。而Prop正是管道和管道之间的一个衔接口,这样(水)数据才能向下流.
看代码:
<!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>Prop</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
</head>
<!--
父组件传给子组件数值,子组件不要对其进行修改,而是需要找个变量将父组件传递的数值给赋值过来
然后对变量进行操作
-->
<body>
<div id="app">
<counter :count='1'></counter>
<counter :count='2'></counter>
</div>
<script>
Vue.component('counter', {
props: ['count'],
data: function() {
return {
number: this.count
}
},
template: '<div @click="add">{{number}}----{{count}}</div>',
methods: {
add: function() {
this.number++,
}
},
})
let vm = new Vue({
el: "#app",
})
</script>
</body>
</html>
父组件传给子组件数值,子组件不要对其进行修改,而是需要找个变量将父组件传递的数值给赋值过来然后对变量进行操作.
上面的代码我就是将prop里面父组件穿的count赋值给number,对number进行操作,并不改变count的值。
但是代码中的方式只适合prop里面的数值是原始类型,不能是对象类型,如果count是对象类型,需要进行深拷贝进行赋值,不然的话,改变number的数值,count的数值还是会改变的
浏览器的显示结果:
2 $emit
触发当前实例上的事件。附加参数都会传给监听器回调。
参数:
{string} eventName
[...args]
这个方法是用来子组件向父组件传递数值的,和上面的Porp正好相反.
下面代码:
<!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>$emit</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
</head>
<!--
父组件传给子组件数值,子组件不要对其进行修改,而是需要找个变量将父组件传递的数值给赋值过来
然后对变量进行操作
-->
<body>
<div id="app">
<counter @inc="addnumber"></counter>
</div>
<script>
Vue.component('counter', {
template: '<div @click="add">点击一下</div>',
methods: {
add: function() {
this.$emit('inc', '温清夜')
}
},
})
let vm = new Vue({
el: "#app",
data: {
total: 5
},
methods: {
addnumber: function(step) {
alert(step)
}
}
})
</script>
</body>
</html>
上面就是$emit的用法,在页面中我点击 '点我一下’就会触发上面绑定的事件add,在add里面有个监听事件inc,因此inc监听事件就会被触发(其实inc事件在代码中就可以看出来,直接在父组件上 @inc=“addnumber”,但是是无效的,因为你需要知道inc是个怎样的东西,到底在哪里。所以在子组件你绑定点击事件,来告诉浏览器inc是个啥东西)
该事件相当于
vm.$on('inc',function addnumber(step){
alert(step)
})
vm.$emit('inc','温清夜')
3 .sync修饰符
这个东西曾经作为双向绑定功能存在,后来Vue2.0移出,然后在Vue2.3版本引入.
它只是作为一个编译时的语法糖存在,它会被扩展为一个自动更新父组件属性的v-on监听器。说白了就是然我们手动进行更新父组件的值,从而使数据改动来源更加明显。官方用语:
在有些情况下,我们可能需要对一个 prop 进行“双向绑定”。不幸的是,真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
既然作为一个语法糖,肯定是某种写法的简写形式:看代码
<text-document
v-bind:title="doc.title"
v-on:update:title="doc.title = $event"
></text-document>
使用.sync语法糖
<text-document v-bind:title.sync="doc.title"></text-document>
注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用 (例如 v-bind:title.sync=”doc.title + ‘!’” 是无效的)。取而代之的是,你只能提供你想要绑定的属性名,类似 v-model。
<!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>.sync</title>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
</head>
<body>
<div id="app">
<login :name.sync="userName"></login> {{ userName }}
</div>
<script>
let Login = Vue.extend({
template: `
<div class="input-group">
<label>姓名:</label>
<input v-model="text">
</div>
`,
props: ['name'],
data() {
return {
text: ''
}
},
watch: {
text(newVal) {
this.$emit('update:name', newVal)
}
}
})
let vm = new Vue({
el: '#app',
data: {
userName: ''
},
components: {
Login
}
})
</script>
</body>
</html>
代码中的一行代码:
this.$emit('update:name', newVal)
官方语法:
我们推荐以 update:myPropName 的模式触发事件取而代之。举个例子,在一个包含 title prop 的假设的组件中,我们可以用以下方法表达对其赋新值的意图:
this.$emit('update:title', newTitle)
myPropName表示要更新的prop值,如果你不用.sync语法糖使用第二种$emit也可以
4 a t t r s 和 attrs和 attrs和listeners
$attrs官网的解释
包含了父作用域中不作为 prop 被识别 (且获取) 的特性绑定 (class 和 style 除外)。当一个组件没有声明任何 prop 时,这里会包含所有父作用域的绑定 (class 和 style 除外),并且可以通过 v-bind="$attrs" 传入内部组件——在创建高级别的组件时非常有用
$listeners官网的解释
包含了父作用域中的 (不含 .native 修饰器的) v-on 事件监听器。它可以通过 v-on="$listeners" 传入内部组件——在创建更高层次的组件时非常有用。
我觉得 a t t r s 和 attrs和 attrs和listeners属性像两个收纳箱,一个负责收纳属性,一个负责收纳事件,都是以对象的形式来保存数据
<!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>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
</head>
<body>
<div id="app">
<child :foo="foo" :bar="bar" @one.native="triggerOne" @two="triggerTwo">
<child>
</div>
<script>
let Child = Vue.extend({
template: '<h2>{{ foo }}</h2>',
props: ['foo'],
created() {
console.log(this.$attrs, this.$listeners)
// -> {bar: "parent bar"}
// -> {two: fn}
// 这里我们访问父组件中的 `triggerTwo` 方法
this.$listeners.two()
// -> 'two'
}
})
new Vue({
el: '#app',
data: {
foo: 'parent foo',
bar: 'parent bar'
},
components: {
Child
},
methods: {
triggerOne() {
alert('one')
},
triggerTwo() {
alert('two')
}
}
})
</script>
</body>
</html>
代码中两个属性个两个方式,区别是,属性是一个prop声明,事件是一个.native修饰器.
我们通过 a t t r s 和 attrs和 attrs和listeners进行数据传递,在需要的地方调用和处理还是比较方便的。
当我们在组件上赋予一个非Prop声明时,编译之后的代码会把这些属性都当成原始属性对待,添加到HTML原生标签上,上面的代码编译后
<h2 bar="parent bar">parent foo</h2>
标签里面的bar属性是什么鬼?如果去掉,这正是inheritAttrs属性的用武之地!给组件加上这个属性就行了,一般是配合$attrs使用:
let Child=Vue.extend({
inheritAttrs:false;//默认是true
})
再次编译
<h2>parent foo</h2>
5 provide/inject
这二个是组合,也就是相互配合使用,缺一不可
官方描述
provide 和 inject 主要为高阶插件/组件库提供用例。并不推荐直接用于应用程序代码中。
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。如果你熟悉 React,这与 React 的上下文特性很相似。
总结:小的时候,你父亲什么东西都帮你存着,等你长大结婚找媳妇的时候,你要买车买房,他有的尽量都会满足你.
<!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">
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<son></son>
</div>
<script>
let Son = Vue.extend({
template: '<h2> son {{house}}--{{car}} ---{{money}}</h2>',
inject: {
house: {
default: '没房'
},
car: {
default: '没车'
},
money: {
//长大工作了虽然有点钱
// 仅供生活费,需要向父母要
default: '¥4500'
}
},
created() {
console.log(this.house, this.car, this.money)
// -> '房子', '车子', '¥10000'
}
})
new Vue({
el: '#app',
provide: {
house: '父亲给的房子',
car: '父亲给的车子',
money: '父亲给的¥10000'
},
components: {
Son
}
})
</script>
</body>
</html>
代码很容易看懂,这个二个东西,一个在子组件 inject
另一个在父组件provide
6 其他通信方式
EventBus
思路就是声明一个全局Vue实例变量EventBus,把所有的通信数据,事件监听都存储到这个变量上,这样就到达在组件间实现数据共享,有点类似Vuex。但是这种方式只适合极小的项目,复杂的项目还是推荐Vuex。
<!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">
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.22/dist/vue.js"></script>
<title>Document</title>
</head>
<body>
<div id="app">
<child>
</child>
</div>
<script>
// 全局变量
let EventBus = new Vue()
// 子组件
let Child = Vue.extend({
template: '<h2>child</h2>',
created() {
console.log(EventBus.message)
// -> 'hello'
EventBus.$emit('received', 'from child')
}
})
new Vue({
el: '#app',
components: {
Child
},
created() {
// 变量保存
EventBus.message = 'hello'
// 事件监听
EventBus.$on('received', function(val) {
console.log('received: ' + val)
// -> 'received: from child'
})
}
})
</script>
</body>
</html>
从显示看,是父子之间双向通信的。
Vuex
官方推荐,VueX是一个专门为Vue.js应用程序开发的状态管理模式
$root
当前组件树的根Vue实例,如果当前实例没有父实例,此实例将会是自己。通过访问根组件也能进行数据之间的交互,但极小情况下回直接修改父组件中的数据.
$parent
父实例,如果当前实例有的话,通过访问父实例也能进行数据之间的交互,但极小情况下回直接修改父组件中的数据
broadcast/dispatch
被删了 ,沃日。大家可以模拟模拟,借鉴借鉴!
- 点赞
- 收藏
- 关注作者
评论(0)