引言
vue的两大特点就是
响应式原理
和组件系统
,今天我们就来看一下关于组件系统
中各级组件是如何传递数据的。
之后的演示都在如下图的关系网中进行:
Father
对于Son
和Daughter
来说为父组件。
Son
对于Father
为子组件,但是对于
GrandSonBySon
和GrandDaughterBySon
为父组件。
Daughter
对于Father
为子组件,但是对于
GrandSonByDau
为父组件。
同时,Daughter
和Son
有同一个父组件
Father
因此这两个组件互为兄弟组件
一、使用prop(父传子)和$emit(子传父)
父组件通过子组件暴露的prop的sonValue、dauValue向子组件传值。
//父组件
<template>
<div class="father">
<son v-bind:sonValue="son"></son>
<daughter v-bind:dauValue="daughter"></daughter>
</div>
</template>
<script>
import Daughter from '@/views/Daughter.vue'
import Son from '@/views/Son.vue'
export default {
components: {
Daughter,
Son
},
data () {
return {
son:'son',
daughter:'daughter'
}
}
}
</script>
子组件通过内部定义的props接收到父组件的值,每当父组件的值变化的时候,子组件内部的值就会发生变化,可以使用this.[props]
来进行引用。
2.0代码
// 子组件
<template>
<div class="father">
{{sonValue}} <!-- son -->
</div>
</template>
<script>
export default {
props:{
sonValue:{
type:String,
default:""
}
},
data () {
return {
}
},
mounted(){
console.log(this.sonValue) // 父组件输入的值'son'
}
}
</script>
3.0 代码
import { defineProps } from 'vue'
const props = defineProps({
sonValue:{
type:String,
default:""
}
})
子组件向父组件传值其实就是触发事件
然后通过事件传参返回给父组件。
2.0代码
// 子组件中触发
this.$emit('事件名','值')
3.0 代码
import { defineEmits } from 'vue
const emits = defineEmits(['事件名'])
// 子组件中触发
emist('事件名','值')
// 父组件中将事件绑定回父实例的函数上,第一个参数为子组件的传值。
<son v-bind:sonValue="son" @changeFather="change"></son>
methods:{
change(value){
//value是子组件的传过来的值
this.fromSon = value
}
}
注意:Vue 单向数据流 的原因所有的 prop 都使得其父子 prop 之间形成了一个单向下行绑定 父级 prop 的更新会向下流动到子组件中,但是反过来则不行。这样会防止从子组件意外改变父级组件的状态,从而导致你的应用的数据流向难以理解。额外的,每次父级组件发生更新时,子组件中所有的 prop 都将会刷新为最新的值。这意味着你不应该在一个子组件内部改变 prop。如果你这样做了,Vue 会在浏览器的控制台中发出警告。
// 子组件son的修改
data () {
return {
//初始化一个子组件内部的值,将传入的值赋值。
realSonValue:this.sonValue
}
},
Vue3.0 中的对于v-model 的修改,将原有的v-model 对于字组件的默认值从value 改成了modelValue 以及可以给v-model 后面增加动态的变量v-model:
变量
来代替了以往的2.0 的.sync。
二、vm.$ref.[child]
和vm.$parent
(父子组件之间的显示调用)
2.0
vm.$ref.[child]
在父组件中引入子组件并且设置子组件的ref值。ref="son"
// 父组件
<template>
<div class="father">
<son v-bind:sonValue="son" @changeFather="change" ref="son"></son>
{{fromSon}}
<daughter v-bind:dauValue="daughter"></daughter>
</div>
</template>
// 父组件都可以通过引用'ref'的方式来获得。
mounted(){
console.log(this.$refs.son.innerValue) // '$refs.son'
this.$refs.son.innerFun() // 'I am Son'
}
子组件内部定义的一个值、或者方法。
data () {
return {
// 初始化一个子组件
innerValue:'$refs.son'
}
},
methods:{
innerFun(){
console.log('I am Son')
}
},
vm.$parent
在子组件中引入父组件实例
// 父组件
data () {
return {
innerFather:'vm.$parent'
}
},
// 子组件
mounted(){
console.log(this.$parent.innerFather) // ·'vm.$parent'
}
注意:有的小伙伴会发现一个问题就是既然有
vm.$parent
那应该也有vm.$children
结果一试还真有,但是在vm中你会发现,一个父组件可以有很多个子组件,也就是有vm.$children
使用这个打印出来的是一个子组件的类数组,无法定位到你要子组件的值,但是每个子组件只有一个父组件这个是固定的。
3.0
在使用选项式API时,我们可以通过this.$refs.name
的方式获取指定元素或者组件,但是组合式API中就无法使用哪种方式获取。如果我们想要通过ref
的方式获取组件或者元素,需要定义一个同名的Ref对象,在组件挂载后就可以访问了
<template>
// 子组件
<son ref='sonRef'></son>
</template>
// js
import { ref } from 'vue'
import Son from './Son.vue'
const sonRef = ref(null)
import { defineExpose } from 'vue'
const value = ref('子组件内部值')
defineExpose({ value })
注意:ref 与 setup 中定义的变量有且只有一个不能重名且后定义的覆盖前面一个。
setup
组件默认是关闭的,也即通过模板ref
获取到的组件的公开实例,但是获取不到子组件内部的属性,如果需要公开需要通过defineExpose
API暴露。
三、bus总线机制(兄弟组件之间)
通过vm.$emit
和vm.$on
通过 vm.$emit
触发当前实例上的事件,并将参数传递给监听器,通过 vm.$on
监听当前实例上的自定义事件。
简单理解为一个托管平台,这个平台必须独立于关联两个组件之外的一个vue实例,当两个组件需要传递数据的时候,发起方在平台上触发一个
vm.$emit
然后传递参数,接收方在平台设置一个监听vm.$on
接收参数并且执行方法,方法的参数就是发送方传递过来的参数。
先定义一个托管平台,vue项目中你可以以单文件的形式在各个兄弟组件中引入,或者像我一样生成一个vue实例挂在整个Vue实例之下。
var bus = new Vue()
Vue.prototype.bus = bus
// 发送方兄弟组件触发$emit设置名字以及传递参数
mounted(){
this.bus.$emit('SonByDauChange','I am grandSonByDau')
}
// 接收方兄弟组件监听事件,并执行回调
methods:{
brotherData(value){
console.log(value) // 'I am grandSonByDau'
}
},
mounted(){
this.bus.$on('SonByDauChange',this.brotherData)
}
3.0 已经移除了事件总线的使用,官方建议使用mitt 等别的第三方库
四、vm.$attrs
和vm.$listeners
props和emit的父子组件传值的进化版。可以从父组件到孙组件的跨级传递,中间的子组件作为
vm.$attrs
和vm.$listeners
的实现载体
vm.$attrs
存放了没有被子组件props所标记的属性,即可以理解为父组件在子组件上自己绑定的数据
2.0
// 父组件引入son子组件
<div>这是父组件</div>
<Son :myData="value" :grandSon="grandSon" @changeGrandSon="changeGrandSon"></Son>
data () {
return {
myData:'from props',
grandSon:'from father'
};
},
methods: {
changeGrandSon(value){
console.log(value) // from grandSon
}
},
// 子组件,能在父组件中被引用的是myData的值,但是我们能通过vm.$attr对象获得props之外的属性
<div>这是子组件</div>
<grandSon v-bind="$attrs" v-on="$listeners"></grandSon> // 划重点可以将父组件给予子组件prop的额外的值传递给孙组件
props:{
myData:{
type:String,
default:''
}
mounted() {
console.log(this.$attrs.granSon) //可以获得from father
},
// 孙组件,不需要props可以直接获得从父组件传递过来的值
<div>这是孙组件</div>
<div>{{$attrs.grandSon}}</div> // from father
mounted() {
console.log(this.$attrs)
this.$emit('changeGrandSon','from grandSon') // 触发父组件的changeGrandSon的事件
},
总结:
vm.$attrs
和vm.$listeners
都可以实现从父组件将值和事件跨级(上下游,兄弟组件不可)传递,但是在中间的组件(子组件)需v-bind="$attrs" v-on="$listeners"
要绑定传值。
3.0
在 Vue 3 的虚拟 DOM 中,事件监听器现在只是以 on
为前缀的 attribute,这样它就成为了 $attrs
对象的一部分,因此 $listeners
被移除了。
<template
<label>
<input type="text" v-bind="$attrs" />
</label>
</template>
<script>
export default {
inheritAttrs: false
}
</script>
如果这个组件接收一个 id
attribute 和一个 v-on:close
监听器,那么 $attrs
对象现在将如下所示:
{
id: 'my-input',
onClose: () => console.log('close 事件被触发')
}
五、provide / inject
这对选项需要一起使用,以允许一个祖先组件向其所有子孙后代注入一个依赖,不论组件层次有多深,并在起上下游关系成立的时间里始终生效。只有数据向下传递,并没有事件响应向上。可以向下跨级传递,子、孙只要有inject均可。
2.0
// 父级组件提供 'foo'
var Provider = {
provide: {
foo: 'bar'
},
// ...
}
// 子、孙组件注入 'foo'
var Child = {
inject: ['foo'],
created () {
console.log(this.foo) // => "bar"
}
// ...
}
3.0
// 父组件
import { ref, provide } from 'vue'
const list = ref(['冲'])
// 向子组件提供数据
provide('list', list.value)
子组件
import {inject} from 'vue'
const list = inject('list')
值得注意的是使用
provide
进行数据传递时,尽量readonly
进行数据的包装,避免子组件修改父级传递过去的数据。
六、使用vuex和localStorage
通过使用数据存储仓库来进行组件的值得交互。建议使用vuex管理大型项目中的各个组件之间的值通讯。可以围绕vuex的状态控制来生成多个组件的关联。具体关于vuex的使用可以查询官方文档
七、结束
单纯提出想法,中间可能有坑更多的实际操作需要各位亲自去尝试,结合api文档。