啥也不多说,直接进入主题
注意:后面给的例子都为实现以下效果(将输入框中的值添加到下面的列表中):
输入一个值并按下确认:
基础实现
先只用一个组件,不进行传值:
js
<template>
<div class="header">
<input type="text" v-model="newMsg">
<button @click="add">确定</button>
</div>
<div class="body">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
const newMsg = ref('')
const list = ref(['html', 'css'])
const add = () => {
list.value.push(newMsg.value)
}
</script>
再简单不过,v-model绑定一下再加个ref就完成了。
父组件给子组件传
1. 父组件传值,子组件通过defineProps接受:
我们创建两个组件parent.vue与child.vue,将body部分放到child.vue,header部分则放在parent.vue。实现代码:
parent.vue:
js
<template>
<div class="header">
<input type="text" v-model="newMsg">
<button @click="add">确定</button>
</div>
<child :list="list"/>
</template>
<script setup>
import { ref } from 'vue';
import child from './fan.vue'
const newMsg = ref('')
const list = ref(['html', 'css'])
const add = () => {
list.value.push(newMsg.value)
}
</script>
child.vue:
js
<template>
<div class="body">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
const props = defineProps({
list:{
type:Array,
default:() => []
}
})
</script>
简单来说就是将父组件的list传给子组件,通过<child :list="list"/>
传出,子组件通过defineProps
接收,这里尤雨溪封装的已经很完美了,也没必要多说什么,是vue中数据传输最常用的手段。但要注意的是当我们在script中使用list时我们需要用props.list,不然响应式将失效。
2. 使用 provide inject 跨组件通信:
与上面一样,创建两个组件parent.vue与child.vue,将body部分放到child.vue,header部分则放在parent.vue。实现代码:
parent.vue:
js
<template>
<div class="header">
<input type="text" v-model="newMsg">
<button @click="add">确定</button>
</div>
<child />
</template>
<script setup>
import { ref, provide } from 'vue';
import child from './fan.vue'
const newMsg = ref('')
const list = ref(['html', 'css'])
const add = () => {
list.value.push(newMsg.value)
}
provide('listSend',list.value)
</script>
child.vue:
js
<template>
<div class="body">
<ul>
<li v-for="item in list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { inject } from 'vue'
const list = inject('listSend')
</script>
父组件把新加的值push进list后直接将 list provide
出去,然后在子组件inject接收。是不是非常方便,且这里provide出去的值所有它的子组件都可以使用,但其实项目中我们很少使用这种方法。当组件树比较大时,这种方法会导致数据来源和依赖关系变得模糊,我们在任何地方都可以修改它的值,很不稳定。另外我们有更好的选择:vuex
,或是其他状态管理模式。
子组件给父组件传
1. 发布订阅机制:
父组件订阅一个事件,子组件发布该事件并且将要传递的值一起发布出来,父组件在定义函数中获取该值,实现代码:
parent.vue:
js
<template>
<child @addMsg="handle"/> // 给接收的事件定义一个订阅名,前面必须是发布的事件名
<div class="body">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
import child from './fan.vue'
const list = ref(['html', 'css'])
const handle = (aa) => { // 父组件订阅一个事件
list.value.push(aa)
}
</script>
child.vue:
js
<template>
<div class="header">
<input type="text" v-model="newMsg">
<button @click="add" type="submit">确定</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
const newMsg = ref('')
const emits = defineEmits(['addMsg'])
const add = () => {
emits('addMsg',newMsg.value) // 发布事件,并且将输入的值与事件一并发出去
}
</script>
在点击按钮时,子组件将addMsg事件带上输入的值发布出去,然后父组件订阅这个事件并接收到输入的值,开始执行,我们可以随意定义这个事件,这里便是将输入的值push到了list中完成了我们的需求。
2. 通过v-model绑定:
父组件v-model绑定属性传给子组件,子组件发布 update:xxx 事件通知父组件数据更新,其实就是在发布订阅上做了些优化,实现代码:
parent.vue:
js
<template>
<child v-model:list="list" />
<div class="body">
<ul>
<li v-for="item in list">{{item}}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
import child from './fan.vue'
const list = ref(['html', 'css'])
</script>
child.vue:
js
<template>
<div class="header">
<input type="text" v-model="newMsg">
<button @click="add">确定</button>
</div>
</template>
<script setup>
import {ref} from 'vue'
const props = defineProps({
list:[]
})
const newMsg = ref('')
const emits = defineEmits(['update:list'])
const add = () => {
props.list.push(newMsg.value)
emits('update:list',props.list)
}
</script>
这种方法带来的便是极简的父组件,我们都知道v-model是双向绑定,这里就相当于把父组件与子组件的list双向绑定了,然后在子组件往list中push值,再将其发布,通知父组件list的值更改了。注意:这里的update:前缀可不能漏了。
3. 通过ref获取DOM结构:
在 Vue 3 中,<script setup>
语法糖提供了一种新特性,即 defineExpose()
函数,允许组件暴露其公共方法或数据,父组件通过ref获取到子组件的dom结构,从而获取到子组件defineExpose()
暴露出来的数据,实现代码:
parent.vue:
js
<template>
<child ref="childRef" />
<div class="body">
<ul>
<li v-for="item in childRef?.list">{{ item }}</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue'
import child from './fan.vue'
const childRef = ref(null)
</script>
child.vue:
js
<template>
<div class="header">
<input type="text" v-model="newMsg">
<button @click="add">确定</button>
</div>
</template>
<script setup>
import { ref } from 'vue'
const newMsg = ref('')
const list = ref(['html', 'css'])
const add = () => {
list.value.push(newMsg.value)
}
defineExpose({ list }) // 子组件心甘情愿暴露list
</script>
细心的朋友可能会发现在循环childRef.list时childRef后面有一个问号,这是因为当你使用 $refs
来引用一个组件实例时,它会出现还未挂载的情况,那么childRef的值就会为空,这毕竟是一个需要时间的进程,此时访问childRef.list
毫无意义,所有我们需要加上一个问号,等挂载完毕后才会去访问。
我的总结到此为止,希望对你有帮助。