我真的了解Vue中的父子组件吗

现在这里有两根金条, 你告诉我哪一根是龌龊的, 哪一根是高尚的
现在有三个 .vue 文件(组件): App.vue, CompA.vue, CompB.vue, 哪一个是父组件, 哪一个又是子组件呢

App.vue 文件:

<template>
    <div id="app">
        <CompA>
            <CompB></CompB>
        </CompA>
        <CompB></CompB>
    </div>
</template>

<script>
import CompA from "@/components/comp-a.vue";
import CompB from "@/components/comp-b.vue";
export default {
    name: "App",
    components: { CompA, CompB },
    methods: {
        fn(v) {
            console.log("$parent: app", v);
        },
    },
};
</script>

CompA.vue 文件:

<template>
    <div class="">
        <div style="width: 300px; height: 300px; background-color: #eee">
            a组件内容
            <slot></slot>
        </div>
    </div>
</template>

<script>
export default {
    name: "CompA",
    methods: {
        fn(v) {
            console.log("$parent: compa", v);
        },
    },
};
</script>

CompB.vue 文件:

<template>
    <div class="">
        <button @click="handleClick">b组件按钮</button>
    </div>
</template>

<script>
export default {
    name: "CompB",
    methods: {
        handleClick() {
            this.$parent.fn("b组件的传值");
        },
    },
};
</script>

页面效果如下:
在这里插入图片描述
采用调用$parent.fn()函数的方式来观察谁是父组件

$root 类似,$parent property 可以用来从一个子组件访问父组件的实例。它提供了一种机会,可以在后期随时触达父级组件,以替代将数据以 prop 的方式传入子组件的方式。 – Vue 2官方文档

当点击灰色区域内的按钮, 即 CompA 组件包裹的 CompB 组件, 控制台打印的是: $parent: compa b组件的传值

当点击白色区域的按钮, 即 App 组件直接引用的 CompB 组件, 控制台打印的是: $parent: app b组件的传值

此时表明, 当 CompB 组件包裹在 CompA 组件内, 则 CompB 组件的直接父组件($parent)是 CompA 组件, 而直接在 App 组件的 template 模板中使用的 CompB 的直接父组件($parent)则是 App 组件

稍作调整, 测试一下传值的规则:

App.vue 文件:

<template>
    <div id="app">
        <CompA>
            <CompB></CompB>
        </CompA>
        <!-- app 组件 向 b 组件传值 -->
        <CompB :count="count"></CompB>
    </div>
</template>

<script>
import CompA from "@/components/comp-a.vue";
import CompB from "@/components/comp-b.vue";
export default {
    name: "App",
    components: { CompA, CompB },
    data() {
        return {
            count: "app-count",
        };
    },
    methods: {
        fn(v) {
            console.log("$parent: app", v);
        },
    },
};
</script>

CompA.vue 文件:

<template>
    <div class="">
        <div style="width: 300px; height: 300px; background-color: #eee">
            a组件内容
            <!-- 尝试在 a 组件中通过 slot 向 b 组件传值 -->
            <slot :count="count"></slot>
        </div>
    </div>
</template>

<script>
export default {
    name: "CompA",
    data() {
        return {
            count: "slot-compa-count",
        };
    },
    methods: {
        fn(v) {
            console.log("$parent: compa", v);
        },
    },
};
</script>

CompB.vue 文件:

<template>
    <div class="">
        <button @click="handleClick">b组件按钮</button>
    </div>
</template>

<script>
export default {
    name: "CompB",
    props: {
        count: {
            type: String,
        },
    },
    methods: {
        handleClick() {
            console.log(this.count)
            this.$parent.fn("b组件的传值");
        },
    },
};
</script>

当点击灰色区域内的按钮, 即 CompA 组件包裹的 CompB 组件, 控制台打印的是: undefined $parent: compa b组件的传值

当点击白色区域的按钮, 即 App 组件直接引用的 CompB 组件, 控制台打印的是: app-count $parent: app b组件的传值

再次调整:

App.vue 文件:

<template>
    <div id="app">
        <CompA>
            <CompB :count="count"></CompB>
        </CompA>
        <!-- app 组件 向 b 组件传值 -->
        <CompB :count="count"></CompB>
    </div>
</template>

当点击灰色区域内的按钮, 即 CompA 组件包裹的 CompB 组件, 控制台打印的是: app-count $parent: compa b组件的传值

当点击白色区域的按钮, 即 App 组件直接引用的 CompB 组件, 控制台打印的是: app-count $parent: app b组件的传值

此时表明, App 组件向两个 CompB 组件传值都是成功的, 无论 CompB 的 $parent 是不是 App.vue

那如何才能将 CompA 组件内的 count 值传递给 CompB 组件呢? 先来梳理一下这三个组件之间的关系:

App.vue 文件: (先只保留一个 CompB 组件)

<template>
    <div id="app">
        <CompA>
            <CompB :count="count"></CompB>
        </CompA>
        <!-- app 组件 向 b 组件传值 -->
        <!-- <CompB :count="count"></CompB> -->
    </div>
</template>

CompA, CompB 和 App 是三个 Vue 组件, 彼此之间没有继承或依赖关系
CompA 是一个具有插槽的父组件, 在它的模板中包含了一个 slot, CompB 是一个具有 props 的子组件, App 是主组件, 将 CompA 加载到其模板中, 当应用程序启动时, Vue 会将 App.vue 编译和渲染为 DOM, 然后将 CompA 组件嵌入到它所定义的位置中, 当渲染 CompA 组件时, Vue 会将 CompB 组件作为其插槽内容进行渲染, 最终, 用户可以看到 CompA 组件的内容和 CompB 组件的按钮
–来自 chatGPT 给我的解释

而到底谁是谁的父组件?

毋庸置疑, CompB 是子组件
App 是父组件,而 CompA 和 CompB 是 App 的子组件
根据组件的引用和注册, 在 App.vue 的模板中引入并注册了 CompA 和 CompB 组件, 因此 App 是父组件,而 CompA 和 CompB 是 App 的子组件
同时, CompA 组件提供了一个插槽, 将 CompB 组件作为其内容进行渲染, 并且结合最开始的实验, CompB 的$parent就是 CompA, 因此 CompA 也可以被看作是 CompB 的父组件
分析到这里, App 有一种即是 CompB 的父组件, 又是 CompB 的爷爷组件的感觉, 为了区分方便, 我就根据 CompB 组件的$parent, 将 CompA 称为 CompB 的直接父组件, App 称为 CompB 的间接父组件(个人区分, 如有错误, 欢迎指正)
但 App 一定是 CompA 的父组件

所以, 想要让 CompA 的值传递给 CompB, 就需要用到作用域插槽, 让 CompA 的父组件 App 组件作为中转
CompA.vue 文件(作为 App 组件的子组件):

<template>
    <div class="">
        <div style="width: 300px; height: 300px; background-color: #eee">
            a组件内容
            <!-- 在 slot 标签上自定义属性 (名字自定义, 这里就叫 count), 和组件内 data 数据中的 count 关联, 值为 "slot-compa-count" -->
            <slot :count="count"></slot>
        </div>
    </div>
</template>

App.vue 文件(作为 CompA 的父组件):

<template>
    <div id="app">
        <CompA>
            <!-- 使用组件, 配合 template 标签和 v-slot 解构出 CopmA 组件中的 count -->
            <template v-slot="{ count }">
                <!-- 将从 CompA 组件中解构出来的 count 值以 count 的属性传递给 CompB 组件, 组件 CompB 内定义的 props 会自动接收 -->
                <CompB :count="count"></CompB>
            </template>
        </CompA>
        <!-- app 组件 向 b 组件传值 -->
        <CompB :count="count"></CompB>
    </div>
</template>

当点击灰色区域内的按钮, 即 CompA 组件包裹的 CompB 组件, 控制台打印的是: slot-compa-count $parent: compa b组件的传值

当点击白色区域的按钮, 即 App 组件直接引用的 CompB 组件, 控制台打印的是: app-count $parent: app b组件的传值

自此, CompA 向 CompB 传值成功

在实际开发中, a 组件中定义 slot 插槽, 在 app 中使用时将 a 组件包裹住(本应该是同级别的) b 组件, 使 a b 组件形成父子组件关系, 这样的用法多见于嵌套组件的封装, 如 <el-form><el-from-item>

<el-form-item>获取<el-form>中的数据, 确实可以通过$parent来获取, <el-form>也可以通过作用域插槽来向<el-form-item>传递数据, 但是使用起来相对而言比较复杂, 同时也不能保证<el-fom-item>外层没有嵌套其他组件, 唯一能保证的就是当<el-form>组件包裹了<el-form-item>组件时, <el-form>组件一定是<el-form-item>组件的上层组件(可能是直接父组件, 也可能是祖先组件), 所以使用provideinject来给这两个组件通信不失为一种更好的选择

App.vue 文件:

<template>
    <div id="app">
        <CompA>
            <CompB></CompB>
        </CompA>
    </div>
</template>

CompA.vue 文件:

<template>
    <div class="">
        <div style="width: 300px; height: 300px; background-color: #eee">
            a组件内容
            <slot></slot>
        </div>
    </div>
</template>

<script>
export default {
    name: "CompA",
    data() {
        return {
            count: {
                value: "provide-compa-count",
            },
        };
    },
    provide() {
        return {
            // 在这里直接定义 count 值并传递, 子孙组件可以接收到, 但子孙组件再通过该组件提供的修改 count 值的 editCount 方法改值, 子孙组件中的接收的 count 并没有变化
            // count: "provide-compa-count",
            // 提示:provide 和 inject 绑定并不是可响应的。这是刻意为之的。然而,如果你传入了一个可监听的对象,那么其对象的 property 还是可响应的。(Vue2官方文档)
            // 多数情况下, 封装组件的时候只需要传递数据给子孙组件即可(或者直接将该组件的 this 传递给子孙组件: CompA: this), 并不需要提供修改数据的方法
            count: this.count,
            editCount: value => {
                this.obj.count = value;
            },
        };
    },
};
</script>

CompB.vue 文件:

<template>
    <div class="">
        <button @click="handleClick">b组件按钮</button>
    </div>
</template>

<script>
export default {
    name: "CompB",
    // 两种写法都可以
    // inject: {
    //     count: {
    //         default: () => {},
    //     },
    //     editCount: {
    //         default: () => {},
    //     },
    // },
    inject: ["obj", "editCount"],
    methods: {
        handleClick() {
            console.log(this.count.value); // provide-compa-count
            this.editCount("123");
            console.log(this.count.value); // 123
        },
    },
};
</script>
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值