父子组件传值【<setup> 语法糖】
KeepAlive 重新创建子组件实例
场景:子组件实例没有被重新创建,因此没有触发 props 的更新
用法
使用 <KeepAlive>
包裹子组件,并加上 :key
强制重新创建。
<KeepAlive>
<ChildComponent :key="Date.now()" :batchNumber="batchNumber"/>
</KeepAlive>
每次传值前改变 key 就会重新创建 ChildComponent 实例。
扩展
在使用 <KeepAlive>
配合 :key 强制重新创建子组件实例的方法中,key 的设置有以下几种方式:
<script setup>
// 1. 标志变量
// ① 定义
let key = 0
// ② 更新
const updateKey = () => {
return key++
}
// ③ 使用
<KeepAlive>
<ChildComponent :key="updateKey" :batchNumber="batchNumber"/>
</KeepAlive>
// 2. 时间戳 - 每次组件重新渲染时都会生成新的时间戳【推荐】
const key = Date.now() // 时间戳
// 可直接使用
< KeepAlive >
< ChildComponent
:
key = "Date.now()"
:
batchNumber = "batchNumber" / >
< /KeepAlive>
// 3. 随机数
const key = Math.random() // 随机数
// 4. 对一个递增计数器取余数作为key
let counter = 0
const updateKey = () => {
return counter++ % 10
}
</script>
父组件向子组件传值
父组件向子组件传值的时候,子组件是通过 props
来接收的, 然后以变量的形式将 props
传递到 setup
语法糖果中使用(defineEmits的到来!)。
1. 父组件传递方式
<template>
<div class="hello">
我是父组件
<!-- 父组件通过 :变量(这里是info)绑定值 -->
<Child :info="parentMsg"></Child>
</div>
</template>
<script setup>
import Child from './Child'
import {ref} from 'vue'
const parentMsg = ref('父组件传递值是 a')
</script>
<style scoped>
</style>
2. 子组件接收并使用
<template>
<!-- info 是父组件传递过来的值 -->
<div>我是子组件,接收到父组件的值是 {{info}}</div>
</template>
<script setup>
import {toRefs, defineProps} from 'vue'
const props = defineProps({
// 子组件接收父组件传递过来的值
info: String,
})
// 使用父组件传递过来的值,注意:可以不使用 toRefs
const {info} = toRefs(props)
</script>
<style scoped>
</style>
子组件向父组件传值【未验证】
vue3 中子组件向父组件传递值和 vue2.x 的区别: vue2.x 使用的是 $emit;而 vue3 使用的是 emit。 它们的传值一样都是方法加值,即 vue2.x 的是 this.$emit('方法名','传递的值(根据需要传或者不传)')
, vue3 的 setup 语法糖的是 defineEmits
1. 子组件的传递方式
<template>
<button @click="handleClickChild">点击子组件</button>
</template>
<script setup>
import {defineEmits} from 'vue'
// 通过 defineEmits 声明组件的 emits
const emit = defineEmits(['clickChild'])
const handleClickChild = () => {
let param = {
content: 'b'
}
// 触发 clickChild 事件,并传递数据给父组件
// 【clickChild 是自定义的,在父组件是 @clickChild="handleClickChild"】
emit('clickChild', param)
}
</script>
<style>
</style>
2. 父组件接收并使用
<template>
<div class="hello">
我是父组件
<!-- clickChild 是子组件绑定的事件,click 是父组件接收方式 -->
<Child @clickChild="clickEvent"></Child>
<p>子组件传递的值是 {{result}}</p>
</div>
</template>
<script setup>
import Child from './Child'
import {ref} from 'vue'
const result = ref('')
const clickEvent = (val) => {
console.log(val);
result.value = val.content
}
</script>
<style scoped>
</style>
父组件获取子组件中的属性值
当时用语法糖时,需要将子组件的属性及方法通过 defineExpose
导出,父组件才能访问到数据,否则拿不到子组件的数据。
1. 子组件的传递方式
<template>
<div>
<h2> 我是子组件</h2>
<p>性别:{{ sex}}</p>
</div>
</template>
<script setup>
import {reactive, ref, defineExpose} from "vue";
let sex = ref('男')
let info = reactive({
like: '王者荣耀',
age: 18
})
defineExpose({sex, info})
</script>
<style>
</style>
2. 父组件直接获取子组件中的属性及方法
<template>
<div class="hello">
我是父组件
<Child ref="childRef"></Child>
<button @click="getChildHandler">获取子组件中的数据</button>
</div>
</template>
<script setup>
import Child from './Child'
import {ref} from 'vue'
const childRef = ref()
const getChildHandler = () => {
console.log('获取子组件中的性别', childRef.value.sex);
console.log('获取子组件中的其他信息', childRef.value.info)
}
</script>
<style scoped>
</style>
子组件向父组件传值【回调】
1. 父组件
<template>
<div class="side-container" @contextmenu.prevent="showContextMenu($event)">
侧边栏
<ContextMenu
:visible="menuVisible"
:menuItems="menuItems"
:top="menuTop"
:left="menuLeft"
@itemClick="handleMenuItemClick"
/>
</div>
</template>
<script setup lang="ts">
import ContextMenu from "../tools/ContextMenu.vue";
import {ref} from "vue";
const menuVisible = ref(false);
const menuTop = ref(0);
const menuLeft = ref(0);
const menuItems = [
{label: "菜单1"},
{label: "菜单2"},
{label: "菜单3"},
{label: "菜单4"},
]
/* 显示菜单 */
const showContextMenu = (e: any) => {
e.preventDefault(); // 阻止浏览器的默认事件
menuTop.value = e.clientY;
menuLeft.value = e.clientX;
menuVisible.value = true;
}
const closeContextMenu = () => {
menuVisible.value = false;
}
/* 处理菜单项点击事件 */
const handleMenuItemClick = (item: any) => {
console.log(`点击了${item.label}`)
closeContextMenu();
}
</script>
<style scoped>
.side-container {
width: 100%;
height: 100%;
background-color: #e9e9e9;
}
</style>
2. 子组件
<!-- 右键菜单 -->
<template>
<div class="context-menu" v-if="visible" :style="{ top: `${top}px`, left: `${left}px` }">
<div v-for="(item, index) in menuItems" :key="index" @click="handleMenuItemClick(item)">
{{ item.label }}
</div>
</div>
</template>
<script setup lang="ts">
defineProps<{
visible: boolean,
menuItems: any,
top: number,
left: number
}>();
const emit = defineEmits(['itemClick']);
const handleMenuItemClick = (item: any) => {
emit('itemClick', item)
}
</script>
<style scoped>
.context-menu {
position: fixed;
background-color: #fff;
border: 1px solid #ccc;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
padding: 8px;
z-index: 1000;
}
</style>
要点:
- 子组件
<template>
<div v-for="(item, index) in menuItems" :key="index" @click="handleMenuItemClick(item)">
{{ item.label }}
</div>
</template>
<script setup lang="ts">
const emit = defineEmits(['itemClick']);
const handleMenuItemClick = (item: any) => {
emit('itemClick', item)
}
</script>
- 父组件
<template>
<div class="side-container" @contextmenu.prevent="showContextMenu($event)">
侧边栏
<ContextMenu
:visible="menuVisible"
:menuItems="menuItems"
:top="menuTop"
:left="menuLeft"
@itemClick="handleMenuItemClick"
/>
</div>
</template>
<script setup lang="ts">
import ContextMenu from "@/tools/ContextMenu.vue";
/* 处理菜单项点击事件 */
const handleMenuItemClick = (item: any) => {
console.log(`点击了${item.label}`)
}
</script>
其中,@itemClick="handleMenuItemClick"
的 @itemClick
是子组件中 defineEmits 的值。 handleMenuItemClick
方法与子组件中方法一致,相当于父组件重写了子组件的方法。【重写一词不准确】
这里给出 ChatGPT
的解释:
当你使用 Vue 3 的组合式 API 中的 <script setup>
语法糖时, 你不再需要像传统的 Vue 组件那样显式地声明 methods
对象来包含你的方法。 相反,你可以使用 defineEmits
从组件的上下文中获取 emit
方法。
在这个例子中,defineEmits
函数接受一个字符串数组,数组中包含了你想要在组件中使用的事件名称, 例如 ['itemClick']
。defineEmits
将返回一个包含了相应事件的 emit
方法的对象。
在上面的代码中,这一行:
const { emit } = defineEmits(['itemClick']);
通过 defineEmits
获取了 emit
方法,并且只保留了与给定事件名称匹配的 emit
方法。 在这个例子中,我们声明了一个名为 itemClick
的事件, 所以通过 defineEmits(['itemClick'])
获取到的 emit
对象只包含了 itemClick
方法。
这样,你就可以在组件中直接使用 emit('itemClick', ...)
,而不需要显式地声明一个 methods
对象。 这是 <script setup>
语法糖的一部分,旨在简化组件代码。