Vue 低代码平台渲染引擎设计
1. 核心架构设计
1.1 整体架构
// 渲染引擎核心接口定义
interface RenderEngine {schema: PageSchema; // 页面Schemacomponents: ComponentMap; // 组件映射dataSource: DataSource; // 数据源events: EventSystem; // 事件系统render(): VNode; // 渲染方法
}// 页面Schema结构
interface PageSchema {id: string;name: string;components: ComponentSchema[];dataSource: DataSourceConfig;events: EventConfig[];style: PageStyle;
}// 组件Schema
interface ComponentSchema {id: string;type: string; // 组件类型componentName: string; // 组件名称props: Record<string, any>; // 组件属性events?: ComponentEvent[]; // 组件事件children?: ComponentSchema[]; // 子组件style?: CSSProperties; // 样式dataBinding?: DataBinding; // 数据绑定
}
2. Schema 设计
2.1 页面 Schema 定义
{"page": {"id": "page_001","name": "用户管理页面","layout": "flex","style": {"padding": "20px","backgroundColor": "#f5f5f5"},"dataSource": {"userList": {"type": "api","url": "/api/users","method": "GET","autoLoad": true}},"components": [{"id": "search_form","type": "container","componentName": "ElForm","props": {"inline": true,"model": "{{searchForm}}"},"children": [{"id": "search_input","type": "input","componentName": "ElInput","props": {"placeholder": "请输入用户名","modelValue": "{{searchForm.keyword}}"},"events": [{"name": "change","action": "SEARCH_USER"}]}]}]}
}
2.2 组件 Schema 扩展
// 详细的组件Schema定义
interface ComponentSchema {id: string;type: ComponentType;componentName: string;props: {// 静态属性[key: string]: any;// 动态属性绑定modelValue?: string; // {{data.field}}// 样式属性class?: string;style?: CSSProperties;};events?: Array<{name: string; // 事件名称action: string; // 动作类型payload?: any; // 动作参数handler?: string; // 自定义处理函数}>;children?: ComponentSchema[];// 数据绑定配置dataBinding?: {type: 'api' | 'local' | 'computed';source: string;field?: string;transform?: string; // 数据转换函数};// 条件渲染conditions?: Condition[];// 循环渲染loop?: {dataSource: string;itemName: string;indexName?: string;};// 插槽配置slots?: {[slotName: string]: ComponentSchema[];};
}// 条件渲染配置
interface Condition {expression: string; // JS表达式operator: '==' | '!=' | '>' | '<' | 'includes' | string;value: any;
}
3. 渲染引擎实现
3.1 核心渲染器
<template><div class="low-code-renderer"><template v-for="component in normalizedComponents" :key="component.id"><component-renderer:schema="component":data="pageData"@action="handleAction"/></template></div>
</template><script setup lang="ts">
import { ref, computed, onMounted } from 'vue';
import ComponentRenderer from './ComponentRenderer.vue';// Props
interface Props {schema: PageSchema;components: ComponentMap;
}
const props = defineProps<Props>();// 页面数据
const pageData = ref<Record<string, any>>({});// 标准化组件数据
const normalizedComponents = computed(() => {return processComponents(props.schema.components, pageData.value);
});// 处理组件(处理循环、条件等)
function processComponents(components: ComponentSchema[], data: any) {return components.flatMap(component => {// 条件渲染检查if (component.conditions && !checkConditions(component.conditions, data)) {return [];}// 循环渲染处理if (component.loop) {const list = getValueByPath(data, component.loop.dataSource) || [];return list.map((item: any, index: number) => ({...component,id: `${component.id}_${index}`,props: processProps(component.props, { ...data, [component.loop.itemName]: item,[component.loop.indexName || 'index']: index })}));}// 普通组件return [{...component,props: processProps(component.props, data)}];});
}// 处理属性中的动态绑定
function processProps(props: Record<string, any>, data: any) {const processed: Record<string, any> = {};Object.keys(props).forEach(key => {const value = props[key];if (typeof value === 'string' && value.startsWith('{{') && value.endsWith('}}')) {// 处理动态绑定 {{data.field}}const expression = value.slice(2, -2).trim();processed[key] = getValueByPath(data, expression);} else {processed[key] = value;}});return processed;
}// 动作处理
function handleAction(action: string, payload: any) {switch (action) {case 'SEARCH_USER':handleSearch(payload);break;case 'SUBMIT_FORM':handleSubmit(payload);break;// 其他动作...}
}// 初始化数据
onMounted(async () => {await loadDataSources();
});// 加载数据源
async function loadDataSources() {if (props.schema.dataSource) {for (const [key, config] of Object.entries(props.schema.dataSource)) {if (config.autoLoad) {pageData.value[key] = await fetchDataSource(config);}}}
}
</script>
3.2 组件渲染器
<template><component:is="resolveComponent(schema.componentName)"v-bind="mergedProps"v-on="eventHandlers"><!-- 渲染子组件 --><template v-if="schema.children && schema.children.length"><component-rendererv-for="child in normalizedChildren":key="child.id":schema="child":data="data"@action="$emit('action', $event)"/></template><!-- 渲染插槽 --><template v-for="(slotComponents, slotName) in schema.slots" :key="slotName"><template #[slotName]><component-rendererv-for="component in slotComponents":key="component.id":schema="component":data="data"@action="$emit('action', $event)"/></template></template></component>
</template><script setup lang="ts">
import { computed } from 'vue';interface Props {schema: ComponentSchema;data: Record<string, any>;
}const props = defineProps<Props>();
const emit = defineEmits<{action: [action: string, payload: any];
}>();// 组件映射
const componentMap = {// 布局组件'ElRow': () => import('element-plus').then(mod => mod.ElRow),'ElCol': () => import('element-plus').then(mod => mod.ElCol),'ElForm': () => import('element-plus').then(mod => mod.ElForm),// 表单组件'ElInput': () => import('element-plus').then(mod => mod.ElInput),'ElButton': () => import('element-plus').then(mod => mod.ElButton),'ElTable': () => import('element-plus').then(mod => mod.ElTable),// 自定义业务组件'UserAvatar': () => import('@/components/UserAvatar.vue'),// ...
};// 解析组件
async function resolveComponent(name: string) {const resolver = componentMap[name];if (!resolver) {console.warn(`Component ${name} not found`);return null;}return await resolver();
}// 处理后的属性
const mergedProps = computed(() => {const baseProps = { ...props.schema.props };// 处理样式if (props.schema.style) {baseProps.style = props.schema.style;}return baseProps;
});// 事件处理器
const eventHandlers = computed(() => {const handlers: Record<string, Function> = {};props.schema.events?.forEach(event => {handlers[event.name] = (...args: any[]) => {emit('action', event.action, {...event.payload,$event: args[0],$data: props.data});};});return handlers;
});// 处理子组件
const normalizedChildren = computed(() => {return processComponents(props.schema.children || [], props.data);
});
</script>
4. 数据源管理
4.1 数据源管理器
class DataSourceManager {private data: Map<string, any> = new Map();private subscribers: Map<string, Function[]> = new Map();// 注册数据源async registerDataSource(config: DataSourceConfig) {const { key, type, options } = config;switch (type) {case 'api':return await this.setupApiDataSource(key, options);case 'local':return this.setupLocalDataSource(key, options);case 'computed':return this.setupComputedDataSource(key, options);}}// API数据源private async setupApiDataSource(key: string, options: ApiDataSourceOptions) {const { url, method = 'GET', params, autoLoad = true } = options;if (autoLoad) {try {const response = await this.fetchData(url, method, params);this.setData(key, response);} catch (error) {console.error(`Failed to load data source ${key}:`, error);}}// 返回数据获取函数return {reload: () => this.fetchData(url, method, params).then(data => {this.setData(key, data);return data;})};}// 设置数据setData(key: string, value: any) {this.data.set(key, value);this.notifySubscribers(key, value);}// 获取数据getData(key: string) {return this.data.get(key);}// 订阅数据变化subscribe(key: string, callback: Function) {if (!this.subscribers.has(key)) {this.subscribers.set(key, []);}this.subscribers.get(key)!.push(callback);// 返回取消订阅函数return () => {const callbacks = this.subscribers.get(key) || [];const index = callbacks.indexOf(callback);if (index > -1) {callbacks.splice(index, 1);}};}private notifySubscribers(key: string, value: any) {const callbacks = this.subscribers.get(key) || [];callbacks.forEach(callback => callback(value));}
}
5. 事件系统
5.1 动作处理器
class ActionHandler {private dataManager: DataSourceManager;private router: Router;constructor(deps: { dataManager: DataSourceManager; router: Router }) {this.dataManager = deps.dataManager;this.router = deps.router;}async handle(action: string, payload: any) {const [actionType, ...rest] = action.split('.');switch (actionType) {case 'NAVIGATE':return this.handleNavigate(payload);case 'API':return this.handleApiCall(payload);case 'DATA':return this.handleDataOperation(payload);case 'MODAL':return this.handleModal(payload);default:console.warn(`Unknown action: ${action}`);}}// 路由跳转private handleNavigate(payload: any) {const { path, query, params } = payload;this.router.push({ path, query, params });}// API调用private async handleApiCall(payload: any) {const { key, url, method, data, onSuccess, onError } = payload;try {const response = await fetch(url, {method,headers: { 'Content-Type': 'application/json' },body: data ? JSON.stringify(data) : undefined});const result = await response.json();if (onSuccess) {await this.handle(onSuccess, { $response: result });}return result;} catch (error) {if (onError) {await this.handle(onError, { $error: error });}throw error;}}// 数据操作private handleDataOperation(payload: any) {const { operation, key, value } = payload;switch (operation) {case 'SET':this.dataManager.setData(key, value);break;case 'UPDATE':const current = this.dataManager.getData(key);this.dataManager.setData(key, { ...current, ...value });break;case 'DELETE':this.dataManager.setData(key, undefined);break;}}
}
6. 动态组件注册
6.1 组件库管理
class ComponentLibrary {private components: Map<string, ComponentDefinition> = new Map();private meta: Map<string, ComponentMeta> = new Map();// 注册组件registerComponent(name: string, definition: ComponentDefinition) {this.components.set(name, definition);}// 注册组件元数据registerComponentMeta(name: string, meta: ComponentMeta) {this.meta.set(name, meta);}// 获取组件getComponent(name: string): ComponentDefinition | undefined {return this.components.get(name);}// 获取组件元数据getComponentMeta(name: string): ComponentMeta | undefined {return this.meta.get(name);}// 获取所有组件getAllComponents(): ComponentMeta[] {return Array.from(this.meta.values());}
}// 组件元数据定义
interface ComponentMeta {name: string;title: string;category: string; // 分类:form、layout、business等props: PropMeta[];events?: EventMeta[];slots?: SlotMeta[];
}interface PropMeta {name: string;type: 'string' | 'number' | 'boolean' | 'array' | 'object';default?: any;required?: boolean;label: string;component?: string; // 用于属性编辑的组件options?: any[]; // 可选值
}// 初始化组件库
export const componentLibrary = new ComponentLibrary();// 注册Element Plus组件
componentLibrary.registerComponentMeta('ElInput', {name: 'ElInput',title: '输入框',category: 'form',props: [{name: 'modelValue',type: 'string',label: '值',required: true},{name: 'placeholder',type: 'string',label: '占位符'},{name: 'disabled',type: 'boolean',label: '禁用',default: false}],events: [{name: 'change',label: '值改变'},{name: 'input',label: '输入'}]
});
7. 渲染优化
7.1 组件缓存和优化
// 组件缓存管理器
class ComponentCache {private cache = new Map<string, any>();private staticComponents = new Set<string>();// 预编译静态组件preCompile(schema: PageSchema) {schema.components.forEach(component => {if (this.isStaticComponent(component)) {this.compileComponent(component);}});}// 判断是否为静态组件private isStaticComponent(component: ComponentSchema): boolean {return !component.dataBinding && !component.conditions && !component.loop &&!hasDynamicProps(component.props);}// 编译组件private compileComponent(component: ComponentSchema) {const key = this.generateKey(component);// 这里可以预编译为渲染函数this.cache.set(key, this.createRenderFunction(component));}// 获取缓存的组件get(key: string) {return this.cache.get(key);}
}// 虚拟滚动优化(针对长列表)
const VirtualListRenderer = defineComponent({props: {components: Array as PropType<ComponentSchema[]>,data: Object},setup(props) {const containerRef = ref<HTMLElement>();const visibleRange = ref({ start: 0, end: 50 });// 监听滚动,计算可见区域const handleScroll = useThrottleFn(() => {if (!containerRef.value) return;const scrollTop = containerRef.value.scrollTop;const clientHeight = containerRef.value.clientHeight;const start = Math.floor(scrollTop / 50);const end = start + Math.ceil(clientHeight / 50) + 5; // 缓冲5个visibleRange.value = {start: Math.max(0, start),end: Math.min(props.components.length, end)};}, 16);const visibleComponents = computed(() => {const { start, end } = visibleRange.value;return props.components.slice(start, end);});return {containerRef,visibleComponents,handleScroll};},template: `<div ref="containerRef" class="virtual-container" @scroll="handleScroll"><div class="virtual-content" :style="{ height: totalHeight + 'px' }"><divv-for="component in visibleComponents":key="component.id"class="virtual-item":style="{ transform: `translateY(${component.index * 50}px)` }"><component-renderer:schema="component":data="data"/></div></div></div>`
});
8. 设计器集成
8.1 设计器与渲染器通信
// 设计器上下文
interface DesignerContext {selectedComponent: string | null;hoveredComponent: string | null;isDesignMode: boolean;updateComponent: (id: string, updates: Partial<ComponentSchema>) => void;deleteComponent: (id: string) => void;addComponent: (parentId: string, component: ComponentSchema) => void;
}// 提供设计器上下文
export const useDesigner = () => {return inject<DesignerContext>('designer');
};// 可编辑组件包装器
const EditableWrapper = defineComponent({props: {schema: Object as PropType<ComponentSchema>,data: Object},setup(props, { slots }) {const designer = useDesigner();const isSelected = computed(() => designer?.selectedComponent === props.schema.id);const handleClick = (e: MouseEvent) => {e.stopPropagation();designer?.selectComponent(props.schema.id);};return () => h('div', {class: ['editable-component',{ 'selected': isSelected.value }],onClick: handleClick}, slots.default?.());}
});
这种设计提供了高度可扩展的低代码渲染架构,支持复杂的业务场景,同时保持良好的性能和开发体验。