vue3子组件内容html与vndoe切换问题

target组件能显示隐藏,其展示内容可通过slotcontent属性定义,如果都定义了则优先contentcontent支持html片段字符串和VNode节点对象;测试发现onMounted中的setVNode没有效果,当target组件显示后,再重新setVNode才有效果,但当再次隐藏target,再显示,发现内容变为空,刚setVNode不见了;setHtml则没问题,这是为啥呢?

test.vue组件代码

<template>
  <div>
    <div>
      <button @click="toggleTarget">
        {{show ? "hide target" : "show target"}}
      </button>
      <button @click="setHtml">
        set html content
      </button>
      <button @click="setVNode">
        set vnode content
      </button>
    </div>
    <target v-model="show" :content="content"></target>
  </div>
</template>
<script lang="ts">
import { defineComponent, ref, h, VNode, onMounted } from 'vue';
import Target from './target.vue';

export default defineComponent({
  components: {
    Target
  },
  setup() {
    const show = ref(false);
    const content = ref<string | VNode>('');
    const toggleTarget = () => {
      show.value = !show.value;
    };
    const setHtml = () => {
      content.value = "<h2>html content</h2>";
    };
    const setVNode = () => {
      content.value = h(
        "h2",
        {},
        "vnode content"
      );
    };
    onMounted(() => {
      //这里没有效果
      setVNode();
    });
    return {
      show,
      toggleTarget,
      content,
      setHtml,
      setVNode
    };
  }
});
</script>

target.vue组件

<template>
  <div style="border:1px solid red;width:200px;height:200px" v-show="visible">
    <div v-html="innerContent" v-if="innerContent">
    </div>
    <div v-else>
      <slot></slot>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, isVNode, nextTick, ref, VNode, watch, h } from 'vue';

export default defineComponent({
  props: {
    content: [String,Object],
    modelValue: {
      type: Boolean,default: false
    }
  },
  setup(props: any) {
    const visible = ref(false);
    const innerContent = ref<string | VNode>('');
    watch(() => props.modelValue,(v) => {
      visible.value = v;
    });
    const internalInstance = getCurrentInstance();
    // if(internalInstance && internalInstance.slots.default === undefined){
    //   internalInstance.slots.default = () => [h('div',{},'w')];
    // }
    // internalInstance && console.log('internalInstance',internalInstance.slots.default);
    watch(() => props.content , (newVal, oldVal) => {
      if(!internalInstance) return;
      if (isVNode(newVal)) {
        internalInstance.slots.default = () => [newVal];
        innerContent.value = '';
      } else {
        if(isVNode(oldVal) && !isVNode(newVal)){
          delete internalInstance.slots.default;
        }
        innerContent.value = newVal;
      }
    });
    return {
      visible,
      innerContent
    };
  }
});
</script>
阅读 3.3k
1 个回答

已解决

  1. slots不能用编程的方式动态去修改(被element的源码坑了);
  2. 用component组件代替,is属性值可以是HTML字符串也可以是VNode;
  3. 如果一个属性只需追踪它的value的变化,用shallowRef代替ref,提高性能;

下面是修改后的代码:

<template>
  <div>
    <div>
      <button @click="toggleTarget">
        {{show ? "hide target" : "show target"}}
      </button>
      <button @click="setHtml">
        set html content
      </button>
      <button @click="setVNode">
        set vnode content
      </button>
    </div>
    <target v-model="show" :content="content"></target>
  </div>
</template>
<script lang="ts">
import { defineComponent, shallowRef, ref, h, VNode, onMounted } from 'vue';
import Target from './target.vue';

export default defineComponent({
  components: {
    Target
  },
  setup() {
    const show = ref(false);
    const content = shallowRef<string | VNode>('');
    const toggleTarget = () => {
      show.value = !show.value;
    };
    const setHtml = () => {
      content.value = "<h2>html content</h2>";
    };
    const setVNode = () => {
      content.value = h(
        "h2",
        {},
        "vnode content"
      );
    };
    onMounted(() => {
      setVNode();
    });
    return {
      show,
      toggleTarget,
      content,
      setHtml,
      setVNode
    };
  }
});
</script>
<template>
  <div style="border:1px solid red;width:200px;height:200px" v-show="visible">
    <component :is="content" v-if="isContentVNode">
    </component>
    <div v-html="content" v-else-if="content">
    </div>
    <div v-else>
      <slot></slot>
    </div>
  </div>
</template>
<script lang="ts">
import { defineComponent, getCurrentInstance, isVNode, nextTick, ref, VNode, watch, h,computed } from 'vue';

export default defineComponent({
  props: {
    content: [String,Object],
    modelValue: {
      type: Boolean,default: false
    }
  },
  setup(props: any) {
    const visible = ref(false);
    watch(() => props.modelValue,(v) => {
      visible.value = v;
    });
    const isContentVNode = computed(() => isVNode(props.content)); 
    return {
      visible,
      isContentVNode
    };
  }
});
</script>
撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题