DSL 渲染器方法详解
基于当前项目 test/demo/src 源码,逐函数详细说明转换过程
一、类型系统 (src/core/types.ts)
1.1 JSExpression
1 2 3 4 5
| interface JSExpression { type: 'JSExpression'; id?: string; value: string; }
|
作用: 表示一段需要在运行时动态求值的 JavaScript 表达式。
value 是字符串形式的表达式代码,渲染时通过 new Function 执行得到实际值。
转换示例:
1 2 3
| 输入: { type: 'JSExpression', value: 'count + 1' } ↓ parseExpression() 执行 输出: number(如 count=5 时结果为 6)
|
1.2 JSFunction
1 2 3 4 5
| interface JSFunction { type: 'JSFunction'; id?: string; value: string; }
|
作用: 表示一段需要在运行时动态生成的 JavaScript 函数。
value 是字符串形式的函数定义代码。
转换示例:
1 2 3
| 输入: { type: 'JSFunction', value: 'function handleClick() { this.count++ }' } ↓ parseFunction() 执行 输出: Function 对象,可直接调用
|
1.3 NodeSchema
1 2 3 4 5 6 7 8 9 10 11 12
| interface NodeSchema { id?: string; name: string; from?: string; locked?: boolean; invisible?: boolean; props?: NodeProps; events?: NodeEvents; directives?: NodeDirective[]; children?: string | JSExpression | NodeSchema[]; slot?: string | NodeSlot; }
|
作用: 描述页面中的每一个组件节点。children 可以是:
string — 纯文本内容,如 "Hello World"
JSExpression — 动态文本,如 { type: 'JSExpression', value: 'message' }
NodeSchema[] — 子节点数组,递归嵌套
1.4 BlockSchema
1 2 3 4 5 6 7 8 9 10 11
| interface BlockSchema { id?: string; name: string; state?: Record<string, JSExpression | JSFunction>; computed?: Record<string, JSFunction>; methods?: Record<string, JSFunction>; watch?: any[]; lifeCycles?: Record<string, JSFunction>; nodes?: NodeSchema[]; css?: string; }
|
作用: 描述一个完整的页面或可复用区块,对应 Vue 的一个 SFC 组件。
1.5 NodeDirective
1 2 3 4 5 6 7 8
| interface NodeDirective { id?: string; name: string | JSExpression; arg?: string | JSExpression; modifiers?: Record<string, boolean>; value?: JSExpression; iterator?: { item: string; index: string }; }
|
1.6 类型守卫函数
1 2 3 4 5 6 7
| function isJSExpression(data: any): data is JSExpression { return data && data.type === 'JSExpression'; }
function isJSFunction(data: any): data is JSFunction { return typeof data === 'object' && data && data.type === 'JSFunction'; }
|
作用: 在运行时判断一个值是否为动态表达式/函数,用于递归遍历时区分静态值和动态值。
二、表达式解析 (src/core/renderer.ts)
2.1 parseExpression(str, self)
1 2 3 4 5 6 7 8 9 10 11 12 13
| function parseExpression(str: JSExpression | JSFunction, self: any) { let tarStr = (str.value || '').trim();
tarStr = tarStr.replace(/this(\W|$)/g, (_a, b) => `__self${b}`);
const code = `with($scope || {}) { "use strict"; var __self = arguments[0]; return ${tarStr} }`;
return new Function('$scope', code)(self); }
|
执行原理:
| 步骤 |
说明 |
str.value |
原始表达式字符串,如 "count + 1" |
this → __self |
替换 this.count 为 __self.count |
with($scope) |
让 $scope 对象的属性可以直接用变量名访问 |
new Function('$scope', code)(self) |
动态创建并立即执行函数 |
self 参数是什么: 即 Context 实例。Context 上有 state、props、methods 等属性,
通过 with 语句,表达式中可以直接写 count 而不是 this.state.count。
完整执行示例:
1 2 3 4 5 6 7 8 9 10 11 12
| 表达式: { type: 'JSExpression', value: 'count + 1' } Context: { state: { count: 5 }, ... }
执行代码: with($scope || {}) { "use strict"; var __self = arguments[0]; return count + 1 }
$scope = Context实例,state 通过 with 展开为可访问变量 结果: 6
|
2.2 parseFunction(str, self)
1 2 3 4 5 6 7
| function parseFunction(str: JSFunction, self: any) { const fn = parseExpression(str, self); if (typeof fn !== 'function') { console.warn('parseFunction.error: not a function', str.value); } return fn as Function; }
|
作用: 与 parseExpression 相同的执行机制,但期望返回值是一个 Function 对象。
示例:
1 2 3 4 5
| 输入: { type: 'JSFunction', value: 'function() { this.count++ }' } ↓ this → __self 替换 ↓ with($scope) 包裹 ↓ new Function 执行 输出: Function 对象,调用时 count 会自增
|
三、运行时上下文 — Context 类
3.1 Context 构造
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class Context { state: Record<string, any> = {}; props: Record<string, any> = {}; $refs: Record<string, any> = {}; $emit: any = null; $slots: any = null; $watch: any = null;
constructor(private options: { dsl?: BlockSchema; attrs?: any }) { if (options.dsl) this.__id = options.dsl.id || null; if (options.attrs) Object.assign(this, options.attrs); } }
|
作用: Context 是 DSL 表达式执行的”宿主环境”,代理了 Vue 组件实例的所有能力。
DSL 中的 this.xxx 实际上就是访问 Context 上的属性。
3.2 ctx.setup(attrs, V) — 绑定 Vue 实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| setup(attrs: Record<string, any>, V: any = Vue) { const instance = V.getCurrentInstance(); if (!instance) return;
this.__refs = {}; this.__refCaches = {}; this.$refs = {};
this.__instance = instance.proxy; Object.assign(this, instance.appContext.config.globalProperties); Object.assign(this, attrs || {});
CONTEXT_HOST.forEach((name) => { this[name] = this.__instance?.[name]; });
V.onMounted(() => { }); V.onUnmounted(() => { }); V.onBeforeUpdate(() => { }); }
|
调用时机: 在 createRenderer 的 setup() 函数内调用,使 Context 与当前 Vue 组件实例绑定。
3.3 ctx.__parseExpression(code) / ctx.__parseFunction(code)
1 2 3 4 5 6 7 8 9
| __parseExpression(code?: JSExpression | JSFunction) { if (!code) return; return parseExpression(code, this); }
__parseFunction(code?: JSFunction) { if (!code) return; return parseFunction(code, this); }
|
作用: Context 实例方法,将自身作为执行上下文传给 parseExpression/parseFunction。
3.4 ctx.__clone(context) — 克隆上下文
1 2 3 4 5 6 7
| __clone(context: Record<string, any> = {}) { const _context = { ...this.context, ...context }; const copy: any = { ..._context, context: _context }; copy.context.__proto__ = this.context; copy.__proto__ = this; return copy as Context; }
|
作用: 创建 Context 的浅克隆,用于 v-for 循环。每次迭代需要独立的上下文来存储 item 和 index 变量。
使用场景:
1 2 3 4
| v-for="item in items" ↓ 每次迭代 ctx.__clone({ item: items[0], index: 0 }) // 子上下文 1 ctx.__clone({ item: items[1], index: 1 }) // 子上下文 2
|
3.5 ctx.__ref(id, ref) — 处理模板引用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| __ref(id: string | null = null, ref?: string | Function) { if (!id) return undefined; let refFunc = this.__refCaches[id]; if (refFunc) return refFunc;
refFunc = async (el: any) => { await new Promise(r => setTimeout(r, 0)); let dom = el?.$el || el; if (id) this.__refs[id] = el; if (typeof ref === 'function') ref(el); else if (ref) this.$refs[ref] = el; }; this.__refCaches[id] = refFunc; return refFunc; }
|
作用: 为每个 DSL 节点创建 Vue 的 ref 回调函数,用于获取 DOM 元素引用。
四、节点渲染 — nodeRender() 核心流程
4.1 函数签名
1 2 3 4 5 6 7 8
| function nodeRender( dsl: NodeSchema, ctx: Context, V: any = Vue, brothers: NodeSchema[], isBranch: boolean, index: number ): any
|
4.2 完整执行流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70
| nodeRender(dsl, ctx, V, brothers, isBranch, index) │ ├─ 1. 前置检查 │ ├─ dsl 为空或 invisible → 返回 null │ └─ 非分支模式遇到 v-else-if/v-else → 返回 null(跳过) │ ├─ 2. 提取指令 │ └─ getDiretives(directives) → { vIf, vElseIf, vElse, vFor, vShow, vBind, vHtml, vModels } │ ├─ 3. 处理 v-if 条件链 │ ├─ vIf 条件为 false → 向后搜索兄弟节点的 v-else-if / v-else │ ├─ 找到匹配的 v-else-if → 递归调用 nodeRender(brother, ctx, V, brothers, true) │ └─ 找到 v-else → 递归调用 nodeRender(brother, ctx, V, brothers, true) │ ├─ 4. 定义内部 render(c, seq) 函数 │ │ │ ├─ 4a. 解析属性 │ │ └─ parseNodeProps(id, dsl.props, c) │ │ ├─ deepParseNodeProps(props, c) // 递归处理 JSExpression │ │ │ ├─ 遇到 JSExpression → c.__parseExpression() 求值 │ │ │ ├─ 遇到 JSFunction → c.__parseFunction() 生成函数 │ │ │ ├─ 遇到 Object → 递归处理每个值 │ │ │ └─ 遇到 Array → 递归处理每个元素 │ │ └─ 设置 ref 回调 c.__ref(id) │ │ │ ├─ 4b. 解析事件 │ │ └─ parseNodeEvents(V, id, dsl.events, c) │ │ ├─ 遍历每个事件 { name, handler: JSFunction } │ │ ├─ c.__parseFunction(handler) → 得到实际函数 │ │ └─ 转为 Vue 格式: { onClick: fn, onInput: fn } │ │ │ ├─ 4c. 处理特殊节点类型 │ │ └─ name === 'slot' → 渲染 Vue slot │ │ │ ├─ 4d. 处理指令效果 │ │ ├─ vBind → Object.assign(props, 解析值) // 合并动态属性 │ │ ├─ vShow → 条件为 false 时设置 style.display = 'none' │ │ ├─ vHtml → 设置 props.innerHTML = 解析值 │ │ └─ vModel → 双向绑定处理(区分原生/组件) │ │ │ ├─ 4e. 处理 v-model 双向绑定 │ │ ├─ 原生 HTML 标签: │ │ │ props.value = 解析值 │ │ │ props.onInput = (v) => { 变量 = v.target.value } │ │ └─ Vue 组件: │ │ props.modelValue = 解析值 │ │ props['onUpdate:modelValue'] = (v) => { 变量 = v } │ │ │ ├─ 4f. 处理子节点 │ │ └─ childrenToSlots(V, dsl.children, c, dsl) │ │ ├─ children 是 string → { default: () => text } │ │ ├─ children 是 JSExpression → { default: () => 动态文本 } │ │ └─ children 是 NodeSchema[] → createSlotsConfig() 分组 │ │ ├─ 按 slot 属性分组 │ │ └─ 每组递归调用 nodeRender() │ │ │ └─ 4g. 创建 VNode │ └─ Vue.createVNode(component, { key, data-vtj, ...props, ...events }, slots) │ ├─ 5. 处理 v-for 循环 │ └─ vForRender(directive, render, ctx) │ ├─ 解析数组: ctx.__parseExpression(value) │ ├─ 遍历每个元素: │ │ ctx.__clone({ item, index }) → 创建子上下文 │ │ render(子上下文, 序号) → 生成 VNode │ └─ 返回 VNode 数组 │ └─ 6. 返回结果 ├─ 有 v-for → VNode 数组 └─ 无 v-for → 单个 VNode
|
4.3 getDiretives() — 指令分类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| function getDiretives(directives: NodeDirective[] = []) { const camelCase = (s: string) => s.replace(/([A-Z])/g, (_, c, i) => i === 0 ? c.toLowerCase() : c);
return { vIf: directives.find(n => camelCase(n.name) === 'vIf'), vElseIf: directives.find(n => camelCase(n.name) === 'vElseIf'), vElse: directives.find(n => camelCase(n.name) === 'vElse'), vFor: directives.find(n => camelCase(n.name) === 'vFor'), vShow: directives.find(n => camelCase(n.name) === 'vShow'), vBind: directives.find(n => camelCase(n.name) === 'vBind'), vHtml: directives.find(n => camelCase(n.name) === 'vHtml'), vModels: directives.filter(n => camelCase(n.name) === 'vModel'), others: directives.filter(n => ) }; }
|
返回值: 将 directives 数组按类型分类,方便后续分别处理。
4.4 deepParseNodeProps() — 深度属性解析
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| function deepParseNodeProps(props: any, ctx: Context): any { if (isJSExpression(props)) return ctx.__parseExpression(props); if (isJSFunction(props)) return ctx.__parseFunction(props); if (Array.isArray(props)) return props.map(i => deepParseNodeProps(i, ctx)); if (typeof props === 'object' && props !== null) { return Object.keys(props).reduce((r, k) => { r[k] = deepParseNodeProps(props[k], ctx); return r; }, {}); } return props; }
|
递归逻辑:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| props: { class: { type: 'JSExpression', value: 'active ? "active" : ""' }, style: { color: 'red', fontSize: { type: 'JSExpression', value: 'size + "px"' } }, items: [ { type: 'JSExpression', value: 'list' }, 'static' ] } ↓ deepParseNodeProps { class: "active", // JSExpression 求值 style: { color: 'red', fontSize: '16px' }, // 嵌套递归 items: [['a','b'], 'static'] // 数组递归 }
|
4.5 parseNodeEvents() — 事件解析
1 2 3 4 5 6 7 8 9 10 11
| function parseNodeEvents(V: any, _id: string, events: NodeEvents, ctx: Context) { return Object.keys(events || {}).reduce((result, key) => { const event = events[key]; const handler = ctx.__parseFunction(event.handler); if (handler) { result['on' + key.charAt(0).toUpperCase() + key.slice(1)] = handler; } return result; }, {}); }
|
转换规则:
1 2 3
| DSL: { click: { handler: JSFunction } } ↓ Vue: { onClick: Function }
|
4.6 vForRender() — v-for 循环渲染
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| function vForRender(directive: NodeDirective, render: (ctx: Context, seq?: number) => any, ctx: Context) { const { value, iterator } = directive; const { item = 'item', index = 'index' } = iterator || {};
let items = ctx.__parseExpression(value) || [];
if (Number.isInteger(items)) items = new Array(items).fill(true).map((_, i) => i + 1);
if (!Array.isArray(items)) return [];
return items.map((_item, _index) => render(ctx.__clone({ [item]: _item, [index]: _index }), _index) ); }
|
执行示例:
1 2 3 4 5 6 7 8 9 10 11 12 13
| 指令: { name: 'vFor', value: { type: 'JSExpression', value: 'items' }, iterator: { item: 'todo', index: 'i' } } items = ['吃饭', '睡觉', '写代码']
↓ 第1次迭代 ctx.__clone({ todo: '吃饭', i: 0 }) → render(clone, 0) → VNode('li', {}, '吃饭')
↓ 第2次迭代 ctx.__clone({ todo: '睡觉', i: 1 }) → render(clone, 1) → VNode('li', {}, '睡觉')
↓ 第3次迭代 ctx.__clone({ todo: '写代码', i: 2 }) → render(clone, 2) → VNode('li', {}, '写代码')
结果: [VNode, VNode, VNode]
|
4.7 childrenToSlots() — 子节点转插槽
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| function childrenToSlots(V: any, children: any, ctx: Context, parent?: NodeSchema): any { if (!children) return null;
if (typeof children === 'string') return { default: () => children };
if (isJSExpression(children)) return { default: () => toString(ctx.__parseExpression(children)) };
if (Array.isArray(children) && children.length > 0) { const slots = createSlotsConfig(children); return Object.entries(slots).reduce((result, [name, { nodes }]) => { result[name] = (scope: any) => { const props = parent?.id && scope ? { [`scope_${parent.id}`]: scope } : {}; return nodes.map((node, index) => nodeRender(node, ctx.__clone(props), V, nodes, false, index)); }; return result; }, {}); } return null; }
|
createSlotsConfig() 分组逻辑:
1 2 3 4 5
| children: [ { name: 'div', slot: 'header', children: '标题' }, → slots.header { name: 'p', slot: 'default', children: '内容' }, → slots.default { name: 'span', slot: 'footer', children: '底部' } → slots.footer ]
|
五、状态初始化函数
5.1 createState() — 创建响应式状态
1 2 3 4 5 6 7 8 9 10 11
| function createState(V: any, state: BlockSchema['state'], ctx: Context) { return V.reactive( Object.keys(state || {}).reduce((result, key) => { let val = state[key]; if (isJSExpression(val)) val = ctx.__parseExpression(val); else if (isJSFunction(val)) val = ctx.__parseFunction(val); result[key] = val; return result; }, {}) ); }
|
转换:
1 2 3
| DSL state: { count: { type: 'JSExpression', value: '0' }, name: { type: 'JSExpression', value: '"Hello"' } } ↓ Vue.reactive({ count: 0, name: 'Hello' })
|
5.2 createComputed() — 创建计算属性
1 2 3 4 5 6
| function createComputed(V: any, computed: Record<string, JSFunction>, ctx: Context) { return Object.entries(computed ?? {}).reduce((result, [k, v]) => { result[k] = V.computed(ctx.__parseFunction(v) as any); return result; }, {}); }
|
转换:
1 2 3
| DSL computed: { double: { type: 'JSFunction', value: 'function() { return this.count * 2 }' } } ↓ { double: Vue.computed(() => count * 2) }
|
5.3 createMethods() — 创建方法
1 2 3 4 5 6
| function createMethods(methods: Record<string, JSFunction>, ctx: Context) { return Object.entries(methods ?? {}).reduce((result, [k, v]) => { result[k] = ctx.__parseFunction(v); return result; }, {}); }
|
转换:
1 2 3
| DSL methods: { increment: { type: 'JSFunction', value: 'function() { this.count++ }' } } ↓ { increment: Function } // 调用时 count 自增
|
5.4 createLifeCycles() — 创建生命周期钩子
1 2 3 4 5 6 7 8 9 10 11 12
| function createLifeCycles(lifeCycle: Record<string, JSFunction>, ctx: Context) { return Object.entries(lifeCycle ?? {}).reduce((result, [k, v]) => { const func = ctx.__parseFunction(v); result[k] = async () => { if (typeof func === 'function') { await new Promise(r => setTimeout(r, 0)); await func(); } }; return result; }, {}); }
|
支持的钩子: onMounted, onUnmounted, onBeforeUpdate, onUpdated 等。
这些会展开为 defineComponent 的选项式钩子。
5.5 setWatches() — 设置侦听器
1 2 3 4 5 6 7 8 9
| function setWatches(V: any, watches: any[], ctx: Context) { watches.forEach((n) => { V.watch( ctx.__parseExpression(n.source), ctx.__parseFunction(n.handler), { deep: n.deep, immediate: n.immediate } ); }); }
|
六、createRenderer() — 渲染器入口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| export function createRenderer(options: CreateRendererOptions) { const V = Vue; const ctx = new Context({ dsl: options.dsl });
const renderer = V.defineComponent({ name: options.dsl?.name || 'DslRenderer', props: {},
async setup() { ctx.state = createState(V, options.dsl?.state, ctx); const computed = createComputed(V, options.dsl?.computed, ctx); const methods = createMethods(options.dsl?.methods, ctx); ctx.setup({ ...computed, ...methods }, V); setWatches(V, options.dsl?.watch, ctx); return { state: ctx.state, ...computed, ...methods }; },
...createLifeCycles(options.dsl?.lifeCycles, ctx),
render() { if (!options.dsl?.nodes) return null; const nodes = options.dsl.nodes || []; if (nodes.length === 1) return nodeRender(nodes[0], ctx, V, nodes); return V.createVNode('div', {}, nodes.map(child => nodeRender(child, ctx, V, nodes)) ); } });
return { renderer: V.markRaw(renderer), context: ctx }; }
|
返回值:
renderer — 用 markRaw 标记的 Vue 组件(避免被 reactive 代理)
context — Context 实例(可用于外部操作)
七、通信协议 (src/canvas/protocol.ts)
7.1 消息格式
1 2 3 4 5
| interface DesignerMessage { channel: '__VTJ_DESIGNER__'; type: MessageType; payload?: any; }
|
7.2 消息类型
| 类型 |
方向 |
说明 |
payload |
init |
Host → Canvas |
初始化 DSL |
BlockSchema |
updateDsl |
双向 |
同步 DSL 变更 |
BlockSchema |
select |
Host → Canvas |
设置选中节点 |
nodeId | null |
nodeClick |
Canvas → Host |
用户点击节点 |
nodeId | null |
dropNode |
Host → Canvas |
拖拽放置组件 |
{ schema, x, y } |
ready |
Canvas → Host |
Canvas 就绪 |
无 |
selectedInfo |
Canvas → Host |
新增节点选中信息 |
{ id, node } |
7.3 sendMessage(target, msg)
1 2 3
| function sendMessage(target: Window, msg: DesignerMessage) { target.postMessage(msg, '*'); }
|
7.4 onMessage(fn) — 监听消息
1 2 3 4 5 6 7 8 9
| function onMessage(fn: (msg: DesignerMessage) => void): () => void { const handler = (e: MessageEvent) => { if (e.data && e.data.channel === CHANNEL) { fn(e.data); } }; window.addEventListener('message', handler); return () => window.removeEventListener('message', handler); }
|
八、代码生成器 (src/coder/index.ts)
8.1 generateVueSFC(dsl) — DSL → Vue SFC 源码
将 BlockSchema 转为 .vue 单文件组件字符串:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| BlockSchema │ ├─ nodes → <template> 部分 │ └─ genNode() 递归生成 HTML 模板 │ ├─ props → HTML 属性 │ ├─ directives → v-if/v-for/v-model 等指令 │ ├─ events → @click/@input 等事件 │ └─ children → 递归子节点 │ ├─ state → <script setup> reactive() ├─ computed → computed() ├─ methods → 普通函数 │ └─ css → <style scoped>
|
8.2 genNode(node, indent) — 节点转模板
1 2 3
| NodeSchema: { name: 'button', props: { class: 'btn' }, children: '点击' } ↓ genNode <button class="btn">点击</button>
|
指令转换:
1 2 3 4 5 6 7 8 9 10 11
| { name: 'vFor', value: 'items', iterator: { item: 'item', index: 'i' } } ↓ v-for="(item, i) in items"
{ name: 'vIf', value: 'show' } ↓ v-if="show"
{ name: 'vModel', value: 'text' } ↓ v-model="text"
|
事件转换:
1 2 3
| { click: { handler: { type: 'JSFunction', value: 'function() { this.count++ }' } } } ↓ @click="() { count++ }"
|
8.3 generateJSON(dsl) — DSL → 格式化 JSON
1 2 3
| function generateJSON(dsl: BlockSchema): string { return JSON.stringify(dsl, null, 2); }
|
直接格式化输出,用于 DSL 查看和导入导出。
九、设计器集成
9.1 DesignerView → Canvas 交互流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| DesignerView (主页面) CanvasApp (iframe) ───────────────────── ─────────────────── 1. iframe 加载 canvas.html 2. iframe load 事件 3. sendToCanvas('init', dsl) ─────────→ 收到 init → renderDsl() 4. createRenderer({ dsl }) 5. <Suspense> 渲染 6. ←─────────── sendToHost('ready') 7. iframeReady = true
用户拖拽物料: 8. onDragStart(schema) 显示蓝色虚线遮罩 9. 鼠标移到画布上方松开 10. onMaskMouseUp() 11. sendToCanvas('dropNode', ...) ──────→ 收到 dropNode 12. addNode() → renderDsl() 13. ←─────────── sendToHost('updateDsl') 14. eng.setDSL(dsl)
用户点击节点: 15. onDocumentClick() 16. ←─────────── sendToHost('nodeClick', id) 17. selectedId = id 18. 右侧 PropsPanel 显示节点配置 19. 用户修改属性 20. eng.updateNodeProp() → syncDslToCanvas() 21. sendToCanvas('updateDsl') ─────────→ 收到 → renderDsl()
|
9.2 CanvasApp 内部渲染流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| CanvasApp.renderDsl() │ ├─ JSON.parse(JSON.stringify(dsl)) // 深拷贝去除 reactive 代理 │ ├─ createRenderer({ dsl }) │ ├─ new Context({ dsl }) │ └─ defineComponent({ setup, render }) │ ├─ renderer.value = result.renderer // 触发 Vue 重新渲染 │ └─ <Suspense> └─ <component :is="renderer" /> ├─ setup() 执行 │ ├─ createState() → reactive state │ ├─ createComputed() → computed │ ├─ createMethods() → methods │ └─ ctx.setup() → 绑定 Vue 实例 │ └─ render() 执行 └─ nodes.map(n => nodeRender(n, ctx, V, nodes)) ├─ 解析 props ├─ 解析 events ├─ 处理 directives ├─ 处理 children/slots └─ createVNode() → VNode 树
|
Prev: 从DSL 到渲染出vue3组件的几种方式
Next: 自有服务器VPS 3x-Ui面板搭建安装详细教程