Vue:以编程方式修改开槽组件的道具

Vue: modify props of slotted component programmatically

问题

如何在运行时修改开槽元素的 prop

我是否使用了错误的生命周期方法?
是否也需要更改 slottedElement.type.props

例子

A.vue

<template>
    <B key="tag-outer">
        <B key="tag-inner" />
    </B>
</template>

<script>
import B from "./B.vue";

export default {
    name: "A",
    components: {
        B,
    },
};
</script>

B.vue

<template>
    <p>{{ mode }}</p>
    <slot />
</template>

<script>
export default {
    name: "B",

    props: {
        mode: {
            type: String,
            default: "outer",
        },

        key: String,
    },

    computed: {
        hasSlot() {
            return !!this.$slots.default;
        },
    },

    beforeMount() {
        this.modifySlottedElements();
    },
    beforeUpdate() {
        this.modifySlottedElements();
    },

    methods: {
        modifySlottedElements() {
            if (this.hasSlot) {
                this.$slots.default().forEach((slottedElement) => {
                    if (slottedElement.type.name === "B") {
                        // prevent concurrency issues
                        const copiedSlottedElement = JSON.parse(JSON.stringify(slottedElement));
                        console.log("before");
                        console.log(copiedSlottedElement);

                        slottedElement.props.mode = "inner";

                        console.log("modified");
                        console.log(slottedElement);
                    }
                });
            }
        },
    },
};
</script>

输出

before

type = Object {name: "B", props: Object, computed: Object, methods: Object, __file: "C:/dev/git/csx-vue/src/demo/test/B.vue", ...}
props = Object {key: "tag-inner"}
...
modified

type = Object {name: "B", props: Object, computed: Object, beforeMount: Function, beforeUpdate: Function, ...}
props = Object {key: "tag-inner", mode: "inner"}
...

已渲染

<p>outer</p>
<p>outer</p>

JSFiddle

谢谢


解决方法

使用反向逻辑(检查父级)

<template>
    <p>{{ modeProxy }}</p>
    <slot />
</template>

<script>
export default {
    name: "B",

    props: {
        mode: {
            type: String,
            default: "outer",
        },

        key: String,
    },

    data() {
        return {
            modeProxy: this.mode,
        };
    },

    beforeMount() {
        this.markSubMenu();
    },
    beforeUpdate() {
        this.markSubMenu();
    },

    methods: {
        markSubMenu() {
            if (this.$parent.$.type.name === "B") {
                this.modeProxy = "inner";
            }
        },
    },
};
</script>

解决问题的最简单方法是使用 inject/provide

  1. 您的菜单组件将使用 inject 从父级(如果有的话)获取它的级别
  2. provide为它的子组件提供有关级别的信息
<template>
  <div>
    Level: {{ menuLevel }} / {{ menuLevel2 }}
    <slot></slot>
  </div>
</template>

<script>
import { provide, inject } from 'vue';

export default {
  name: 'NestedMenu',
  setup() {
    // using Composition API
    let menuLevel = inject('menuLevel', 1 /* this is default value */);
    provide('menuLevel', menuLevel + 1);

    return { menuLevel };
  },
  // Same functionality as above but using Options API
  inject: {
    menuLevel2: { default: 1 },
  },
  provide() {
    return {
      menuLevel2: this.menuLevel2 + 1,
    };
  },
};
</script>