前段时间给项目的无代码系统做了一个交互预览页面,主要功能是把UI信息的JSON解析渲染为可交互的网页,其中遇到了不同类型组件渲染的问题
一、为什么需要动态组件渲染
我们的无代码系统采用PCB协议(Page、Container和Block),为了实现交互,Container分为有焦点和无焦点,Block作为最基础的页面元素,其种类包含图片、文本、视频等16种,如果又要保证页面结构简单明晰,又要让Block有较高的灵活实现的话,就避不开是用动态组件。
二、动态组件的实现方式
对于上面所说的情况,实际中针对不同的场景采取了两种不同的实现方式。
1. JSX结合if判断实现
这种方式是用于解决Container的两种不同组件,即有焦点和无焦点,由于这两个组件有较大的功能差异,分成了单独的两个组件实现,同时在props
上也有一定区别,上述情况综合考虑,使用了JSX结合if判断进行条件渲染实现,大致实现如下:
<script>
import FocusContainer from "./focus-container.vue";
import NoFocusContainer from "./no-focus-container.vue";
export default {
name: "Container",
components: {
FocusContainer,
NoFocusContainer
},
render() {
const { hasfocus } = this.uiData;
return hasfocus ? (
<FocusContainer
uiData={this.uiData}
isActive={this.isActive}
></FocusContainer>
) : (
<NoFocusContainer
uiData={this.uiData}
></NoFocusContainer>
);
}
};
</script>
总的来说,这种适合组件总数数量少、props有差异、没有后续改变或频繁改变的场景。
2. Component is动态组件实现
第一种方式对于只有两个类型的Container还相对容易,但是对于实际中类型的Block要是写起if…else或者switch那也是相当难维护,所以第二种方式是用Vue官方提供的is
属性实现动态组件,没有印象的可以先看看组件基础 — Vue.js动态组件 & 异步组件 — Vue.js,大致实现如下:
<template>
<component
:is="blockType"
:uiData="uiData"
></component>
</template>
<script>
import BlockCommonPropsMixin from "./block-common-props-mixin";
import ImageBlock from "./image/image-block.vue";
import TextBlock from "./text/text-block.vue";
import {
TEXT_BLOCK,
IMAGE_BLOCK
} from "./block-type";
const componentMap = {
[TEXT_BLOCK]: TextBlock,
[IMAGE_BLOCK]: ImageBlock,
};
export default {
name: "Block",
mixins: [BlockCommonPropsMixin],
computed: {
blockType() {
const { type } = this.uiData;
return componentMap[type];
}
}
};
</script>
先将类型和组件对象存储在一个Map中,然后声明一个计算属性返回对应的组件对象,将这个作为component
的is
属性的值即可,这里有几点注意的:
1. 将Block的类型放在一个单独的文件中,方便后续的维护;
2. Block和其他类型的Block的props可以提取成一个mixin文件;
3. 如果后续(组件类型)有变化,可以使用keep-alive包裹实现缓存。
总的来说,这种方式适用于组件数量多、组件props基本一致的情况。