父子组件传值【<setup> 语法糖】

Siona

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> 语法糖的一部分,旨在简化组件代码。

Last Updated 3/2/2024, 4:00:59 PM