Skip to content

h 函数定义和使用插槽

​ 使用 h 函数用来创建渲染虚拟 DOM,可以封装成函数,可以通过 JS 中调用快速渲染出一个组件。可是 h 函数如何定义和使用插槽呢?

1.定义插槽

使用renderSlots来定义插槽,其原理就是通过上下文中的 slots 对象获取某个具名插槽的 key 来渲染出该插槽的内容

js
const components = {
  setup(_, { slots }) {
    return () => {
      h("div", null, [
        renderSlots(slots, "default"), // 默认插槽
        renderSlots(slots, "footer"), // 具名插槽,名为:footer
        renderSlots(slots, "header"), // 具名插槽,名为:header
      ]);
    };
  },
};
const components = {
  setup(_, { slots }) {
    return () => {
      h("div", null, [
        renderSlots(slots, "default"), // 默认插槽
        renderSlots(slots, "footer"), // 具名插槽,名为:footer
        renderSlots(slots, "header"), // 具名插槽,名为:header
      ]);
    };
  },
};

也可以获取到对应渲染函数直接调用函数一样可以。

js
const components={
	setup(_,{slots}){
		return ()=>{
            h('div',null,[
              h('div',slots?.default()),
              slots?.footer()
              slots?.header()
            ])
        }
	}
}
const components={
	setup(_,{slots}){
		return ()=>{
            h('div',null,[
              h('div',slots?.default()),
              slots?.footer()
              slots?.header()
            ])
        }
	}
}

2.使用插槽

js
h(
  components,
  null, // 这一项必须为null,否则会插槽会被认为是props、attrs
  {
    default: () => h("div", "hello~"),
    footer: () => h("div", "hello~"),
    header: () => h("div", "hello~"),
  }
);
h(
  components,
  null, // 这一项必须为null,否则会插槽会被认为是props、attrs
  {
    default: () => h("div", "hello~"),
    footer: () => h("div", "hello~"),
    header: () => h("div", "hello~"),
  }
);

3.特殊情况

​ 如果说我们通过 h 函数渲染的组件有第三方插件例如i18n会在模板中使用$t来渲染国际化文本就会导致因为虚拟 DOM 无$t从而渲染失败而报错。解决方案:

1.新建 App 实例(不推荐)

​ 我们可以通过 createApp 新建一个 app 实例,并通过 app.use 按照插件,再通过 app 组件的 render 函数来渲染组件。

但是这样多多少少都会有内容开销,毕竟一个 app 实例+插件是是否繁重的。

下面是一个模态框的封装:

ts
import { createApp, h, render, renderSlot } from "vue";
import type { Component } from "vue";
import "./index.css";
import i18n from "@/config/i18n";

export function renderModal(content: Component, props: Record<any, any>) {
  // 遮罩层
  const mask = document.createElement("div");
  mask.classList.add("modal-mask-container");

  const app = createApp({
    name: "ModalApp",
    render() {
      return container;
    },
  });
  // 实际组件(模态框)
  const container = h(
    // 定义
    {
      name: "ModalContainer",
      setup(_, context) {
        return () =>
          h(
            "div",
            {
              class: "modal-container",
            },
            [renderSlot(context.slots, "default")]
          );
      },
    },
    null,
    // 使用
    {
      default: () =>
        h(content, {
          ...props,
          onClose() {
            console.log("关闭事件触发了!");
          },
        }),
    }
  );
  // 按照插件
  app.use(i18n);
  // 将虚拟DOM渲染再真实DOM中
  app.mount(mask);

  document.body.appendChild(mask);
}
import { createApp, h, render, renderSlot } from "vue";
import type { Component } from "vue";
import "./index.css";
import i18n from "@/config/i18n";

export function renderModal(content: Component, props: Record<any, any>) {
  // 遮罩层
  const mask = document.createElement("div");
  mask.classList.add("modal-mask-container");

  const app = createApp({
    name: "ModalApp",
    render() {
      return container;
    },
  });
  // 实际组件(模态框)
  const container = h(
    // 定义
    {
      name: "ModalContainer",
      setup(_, context) {
        return () =>
          h(
            "div",
            {
              class: "modal-container",
            },
            [renderSlot(context.slots, "default")]
          );
      },
    },
    null,
    // 使用
    {
      default: () =>
        h(content, {
          ...props,
          onClose() {
            console.log("关闭事件触发了!");
          },
        }),
    }
  );
  // 按照插件
  app.use(i18n);
  // 将虚拟DOM渲染再真实DOM中
  app.mount(mask);

  document.body.appendChild(mask);
}

2.直接导入 i18n 插件来使用

​ 对应组件中直接导入i18n插件在模板中使用,就不再需要 app 实例了,因为我们的组件模板没有使用$t来渲染文本就不会产生错误了。

下面是一个模态框的封装: 这样就非常好了。

js
import { h, render, renderSlot } from "vue";
import type { Component } from "vue";
import "./index.css";

/**
 * 渲染模态框
 * @param content 模态框内容组件
 * @param props 模态框内容组件的props
 */
export function renderModal(content: Component, props: Record<any, any>) {
  // 容器
  const mask = document.createElement("div");
  mask.classList.add("modal-mask-container");

  // 虚拟DOM
  const container = h(
    // 定义组件
    {
      name: "ModalContainer",
      setup(_, context) {
        return () =>
          h(
            "div",
            {
              class: "modal-container",
            },
            [renderSlot(context.slots, "default")]
          );
      },
    },
    null,
    // 使用组件
    {
      default: () =>
        h(content, {
          ...props,
          onClose() {
            console.log("关闭事件触发了!");
          },
        }),
    }
  );

  // 渲染
  render(container, mask);
  // 挂载
  document.body.appendChild(mask);
}
import { h, render, renderSlot } from "vue";
import type { Component } from "vue";
import "./index.css";

/**
 * 渲染模态框
 * @param content 模态框内容组件
 * @param props 模态框内容组件的props
 */
export function renderModal(content: Component, props: Record<any, any>) {
  // 容器
  const mask = document.createElement("div");
  mask.classList.add("modal-mask-container");

  // 虚拟DOM
  const container = h(
    // 定义组件
    {
      name: "ModalContainer",
      setup(_, context) {
        return () =>
          h(
            "div",
            {
              class: "modal-container",
            },
            [renderSlot(context.slots, "default")]
          );
      },
    },
    null,
    // 使用组件
    {
      default: () =>
        h(content, {
          ...props,
          onClose() {
            console.log("关闭事件触发了!");
          },
        }),
    }
  );

  // 渲染
  render(container, mask);
  // 挂载
  document.body.appendChild(mask);
}