[动态界面 json2render] 动态表单中使用自定义容器组件

644 阅读3分钟

需求

上次碰到一个需求要求动态表单配置中加载另一个动态表单配置的组件,利用 json2render 已经能实现这个功能,具体实现 [动态界面 json2render] 动态界面中加载自定义模板组件

这次需求又升级了,要求动态配置的组件具有容器 slot 特性,内部还可以放组件进去,就是需要定义多个 slot 后在引用组件时候将 children 组件放到对应 slot 里,需求主要是解决能通过配置定义具有内部布局的自定义组件,实现只提供几种简单的布局组件配合样式就能创建多种风格的组件

上一个版本的 json2render 还不支持这个特性,这个功能后来也加上了

效果

先看下最后要达到的效果,本示例基于 Vue3

动态组件的配置定义如下

{
  "fields": [
    {
      "component": "h1",
      "props": {
        "style": { "fontSize": "40pt" }
      },
      "children": [
        { "component": "span", "text": "标题: " },
        // 具有命名的 slot
        { "component": "slot", "name": "header" }
      ]
    },
    { "component": "p", "text": "正文" },
    // 默认的 slot,并将 props 传递给 scope 给 children 用
    { "component": "slot", "props": { "attrValue": "aaaaa" } }
  ]
}

引用动态组件,并在内部对应 slot 显示内容

{
  "datasource": {
    // 用于远程加载动态组件的数据源,自动加载
    "template": {
      "type": "fetch",
      "url": "/data/components/slotcmp.json",
      "auto": true,
      "props": { "method": "GET", "params": {} },
      "defaultData": false
    }
  },
  "fields": [
    {
      "component": "div",
      "children": [
        {
          "component": "v-jrender",
          "condition": "$:template.data",
          "props": "$:template.data",
          // v-jrender 之前不支持设置 children,现在提供支持
          "children": {
            // 默认 slot
            "default": [
              // 利用转换表达式获取父级传过来的 scope 里 attrValue 属性值
              { "component": "span", "text": "#:inner text ${scope.attrValue}" }
            ],
            // 命名 slot
            "header": [{ "component": "span", "text": "标题slot内容" }]
          }
        }
      ]
    }
  ]
}

最后效果如下

008i3skNly1gsfj8yuzmxj30w60dg3zx.jpeg

如果直接使用 v-jrender 组件,而配置里包含有 component 是 slot 的节点也可直接在 v-jrender 的 children 加入内容

<script setup>
import { ref } from 'vue'

const fields = ref([{
    component: 'div',
    children: [{
        component: 'slot'
    }]
}])
</script>
<template>
    <v-jrender :fields="fields">
        <!-- slot 处渲染的内容 -->
        <div>content</div>
    </v-jrender>
<template>

原理

插槽概念

vue 具有插槽特性,在定义组件时可在某个位置加一个插槽 slot,当使用该组件时在组件的 children 节点会渲染到定义插槽的位置

<button type="submit">
  <slot></slot>
</button>

插槽还可以命名,让 children 节点渲染到具有特定名称的位置

<div class="container">
  <header>
    <slot name="header"></slot>
  </header>
  <main>
    <slot></slot>
  </main>
  <footer>
    <slot name="footer"></slot>
  </footer>
</div>

vue 运行时会得到声明的 slot,在 vue3 运行时实例具有一个 slots 对象,成员是以组件内 slot 命名的方法集合,该方法集合都会返回 vnode 数组,分别是定义在对应 slot 里的 children

// 当前对象 slots 成员
const ctx = {
  slots: {
    default(scope) {
      // 返回 vnodes
    },
    header(scope) {
      // 返回 vnodes
    },
    footer(scope) {
      // 返回 vnodes
    }
  }
}

json2render 基于 Vue 的渲染扩展

json2render 基于 vue3 的实现提供了在每个节点 setup 环节的 hook 扩展操作,这个操作可以实现在渲染前对节点进行预处理,根据条件改变节点属性,这个处理只会在节点首次初始化过程中执行,不会影响界面响应数据变化时候的性能

hook 的定义方法如下

() => (field, next) => {
  // field 是当前节点的定义
  // 这里可以对 field 的属性进行预处理,变更属性
  next(field)
}

如果想支持 slot 特性需要实现一个 hook,具体过程是父节点执行 hook 时遍历所有 children,当 child 的 component 属性值是 slot 时认定该节点是 slot 节点用来显示 slot 的内容,用 child 的 name 属性(默认default)查找 slots 里对应名称的方法并执行,用返回的 vnode 集合替换 children 对应的元素,在渲染的时候会就可实现在特定 slot 位置渲染对应 children

关于

关于 json2render 实现原理涉及到东西很多,在最新版本里主要就是增加了 dependency injection 机制,使之能够合理的扩展新功能以及在所有子节点中共享在 children 定义的的 slots,具体参考项目 json2render,网慢访问 国内镜像