From 18dcb258d44e2c5670b13d1b2be7d414a3c1d864 Mon Sep 17 00:00:00 2001 From: yy Date: Fri, 5 Jun 2026 17:17:23 +0800 Subject: [PATCH 1/3] feat: implement state accessor management with useAccessorMap and useState for improved state handling --- renderer/RenderMain.js | 22 +++++---- renderer/accessor.ts | 101 +++++++++++++++++++++++++++++++++++++++++ renderer/dataUtils.ts | 24 ++++++++++ renderer/state.ts | 47 +++++++++++++++++++ renderer/types.ts | 36 +++++++++++++++ 5 files changed, 222 insertions(+), 8 deletions(-) create mode 100644 renderer/accessor.ts create mode 100644 renderer/dataUtils.ts create mode 100644 renderer/state.ts create mode 100644 renderer/types.ts diff --git a/renderer/RenderMain.js b/renderer/RenderMain.js index 4c73000..9ab10ca 100644 --- a/renderer/RenderMain.js +++ b/renderer/RenderMain.js @@ -19,6 +19,9 @@ import { setPageCss } from './pageCss' import { RENDERER_SETTINGS_KEY } from './renderer-settings' import useCustomSetting from './useCustomSetting' import { getPageLifeCycleFns } from './lifeCycles' +import { useAccessorMap } from './accessor' +import { useState } from './state' +import { reset } from './dataUtils' export default { props: { @@ -35,9 +38,6 @@ export default { }); const { context, oldSchema, setContext, getContext } = useContext() const cssScopeId = `data-schema-${Math.random().toString(36).slice(2, 8)}` - const reset = (obj) => { - Object.keys(obj).forEach((key) => delete obj[key]) - } // 设置 customSettings,如 Function const { setCustomSettings } = useCustomSetting() @@ -56,7 +56,8 @@ export default { const pageSchema = reactive({}) const methods = {} - const state = reactive({}) + const { clearStateAccessors, registerStateAccessors } = useAccessorMap(getContext) + const { state, setState: applyState } = useState({ getContext, registerStateAccessors }) const refs = shallowReactive({}) let pageOnUnmounted = null @@ -88,12 +89,13 @@ export default { } const setState = (data, clear) => { - clear && reset(state) + if (clear) { + clearStateAccessors() + } if (!pageSchema.state) { pageSchema.state = data } - - Object.assign(state, parseData(data, {}, getContext()) || {}) + applyState(data, clear) } const setRefs = (data, clear) => { @@ -109,6 +111,9 @@ export default { if (!data || !Object.keys(data).length) { return } + + clearStateAccessors() + const newSchema = JSON.parse(JSON.stringify(data)) const context = { state, @@ -122,7 +127,7 @@ export default { setMethods(newSchema.methods, true) // 这里setState(会触发画布渲染),是因为状态管理里面的变量会用到props、utils、bridge、stores、methods - setState(newSchema.state, true) + applyState(newSchema.state) setRefs(newSchema.refs, true) await invokePageOnUnmounted() @@ -144,6 +149,7 @@ export default { } onUnmounted(async () => { + clearStateAccessors() await invokePageOnUnmounted() }) diff --git a/renderer/accessor.ts b/renderer/accessor.ts new file mode 100644 index 0000000..1b2169c --- /dev/null +++ b/renderer/accessor.ts @@ -0,0 +1,101 @@ +import { watchEffect, type WatchStopHandle } from 'vue' +import { parseData, isStateAccessor } from './render' +import type { GetContext, StateAccessorConfig } from './types' + +type AccessorType = 'getter' | 'setter' + +interface AccessorDescriptor { + getter?: { value?: string } + setter?: { value?: string } + [key: string]: unknown +} + +/** + * 创建状态访问器管理器,用于注册 getter/setter 的 watchEffect 监听。 + * + * @param getContext - 获取页面运行时上下文 + */ +export function useAccessorMap(getContext: GetContext) { + const stateAccessorMap = new Map() + + /** + * 解析 accessor 中的 getter 或 setter 为可执行函数。 + * + * @param type - 访问器类型 + * @param accessor - 访问器配置 + * @param property - 状态变量名 + */ + const generateAccessor = (type: AccessorType, accessor: AccessorDescriptor, property: string) => { + const accessorFn = parseData(accessor[type], {}, getContext()) + + return { property, accessorFn, type } + } + + /** + * 为指定状态变量注册 getter 或 setter 的 watchEffect 监听。 + * schema 更新时会先取消同 key 的旧监听,避免重复注册造成数据混乱。 + * + * @param type - 访问器类型 + * @param accessor - 访问器配置 + * @param key - 状态变量名 + */ + const generateStateAccessors = (type: AccessorType, accessor: AccessorDescriptor, key: string): void => { + const stateWatchEffectKey = `${key}${type}` + const { property, accessorFn } = generateAccessor(type, accessor, key) + + if (typeof accessorFn !== 'function') { + console.warn(`状态变量 ${property} 的 ${type} 解析失败,跳过注册`) + return + } + + stateAccessorMap.get(stateWatchEffectKey)?.() + + stateAccessorMap.set( + stateWatchEffectKey, + watchEffect(() => { + try { + accessorFn() + } catch (error) { + console.warn(`状态变量 ${property} 的访问器函数执行报错`, error) + } + }) + ) + } + + /** + * 取消所有已注册的状态访问器监听。 + */ + const clearStateAccessors = (): void => { + stateAccessorMap.forEach((stop) => stop()) + stateAccessorMap.clear() + } + + /** + * 遍历 state 配置,为带 accessor 的字段注册 getter/setter 监听。 + * + * @param data - schema.state 原始配置 + */ + const registerStateAccessors = (data: Record): void => { + Object.entries(data || {}).forEach(([key, stateData]) => { + if (!isStateAccessor(stateData)) { + return + } + + const accessor = (stateData as StateAccessorConfig).accessor + + if (accessor?.getter?.value) { + generateStateAccessors('getter', accessor, key) + } + + if (accessor?.setter?.value) { + generateStateAccessors('setter', accessor, key) + } + }) + } + + return { + generateStateAccessors, + clearStateAccessors, + registerStateAccessors + } +} diff --git a/renderer/dataUtils.ts b/renderer/dataUtils.ts new file mode 100644 index 0000000..0ee6b6c --- /dev/null +++ b/renderer/dataUtils.ts @@ -0,0 +1,24 @@ +/** + * 清空对象上的所有可枚举属性。 + * + * @param obj - 待清空的对象 + */ +export const reset = (obj: Record): void => { + Object.keys(obj).forEach((key) => delete obj[key]) +} + +/** + * 对比两个对象,返回 objA 中存在但 objB 中不存在的 key 列表。 + * + * @param objA - 当前运行时对象 + * @param objB - 新的 schema 配置对象 + * @returns 需要从运行时对象中删除的 key + */ +export const getDeletedKeys = ( + objA: Record | null | undefined, + objB: Record | null | undefined +): string[] => { + const keyB = new Set(Object.keys(objB || {})) + + return Object.keys(objA || {}).filter((item) => !keyB.has(item)) +} diff --git a/renderer/state.ts b/renderer/state.ts new file mode 100644 index 0000000..4f7ff5e --- /dev/null +++ b/renderer/state.ts @@ -0,0 +1,47 @@ +import { shallowReactive } from 'vue' +import { parseData } from './render' +import { getDeletedKeys, reset } from './dataUtils' +import type { GetContext } from './types' + +interface UseStateOptions { + getContext: GetContext + registerStateAccessors: (data: Record) => void +} + +/** + * 创建页面级 state 管理器。 + * + * @param options - 配置项 + */ +export function useState({ getContext, registerStateAccessors }: UseStateOptions) { + const state = shallowReactive>({}) + + /** + * 将 schema.state 同步到运行时 state,并注册 accessor。 + * + * @param data - schema.state 原始配置 + * @param clear - 是否全量清空后再写入(外部 API 兼容) + */ + const setState = (data: Record, clear?: boolean): void => { + if (typeof data !== 'object' || data === null) { + return + } + + if (clear) { + reset(state) + } else { + const deletedKeys = getDeletedKeys(state, data) + for (const key of deletedKeys) { + delete state[key] + } + } + + Object.assign(state, parseData(data, {}, getContext()) || {}) + registerStateAccessors(data) + } + + return { + state, + setState + } +} diff --git a/renderer/types.ts b/renderer/types.ts new file mode 100644 index 0000000..7d2dc47 --- /dev/null +++ b/renderer/types.ts @@ -0,0 +1,36 @@ +/** + * Schema 协议中的 JSFunction 描述符。 + */ +export interface JSFunctionDescriptor { + type: 'JSFunction' + value: string +} + +/** + * 带 getter/setter 的状态访问器配置。 + */ +export interface StateAccessorConfig { + accessor?: { + getter?: JSFunctionDescriptor + setter?: JSFunctionDescriptor + } + defaultValue?: unknown +} + +/** + * 渲染器自定义配置,通过 provide(RENDERER_SETTINGS_KEY) 注入。 + */ +export interface RendererSettings { + Function?: FunctionConstructor + transformJSX?: (code: string) => string +} + +/** + * 页面运行时上下文。 + */ +export type PageContext = Record + +/** + * 获取页面运行时上下文的函数。 + */ +export type GetContext = () => PageContext From 1c7f01cd27450c46d6a898a37ea09e6e6619cffe Mon Sep 17 00:00:00 2001 From: yy Date: Mon, 8 Jun 2026 09:57:19 +0800 Subject: [PATCH 2/3] refactor: remove unused RendererSettings interface from types.ts to streamline configuration --- renderer/types.ts | 8 -------- 1 file changed, 8 deletions(-) diff --git a/renderer/types.ts b/renderer/types.ts index 7d2dc47..cf8fa0c 100644 --- a/renderer/types.ts +++ b/renderer/types.ts @@ -17,14 +17,6 @@ export interface StateAccessorConfig { defaultValue?: unknown } -/** - * 渲染器自定义配置,通过 provide(RENDERER_SETTINGS_KEY) 注入。 - */ -export interface RendererSettings { - Function?: FunctionConstructor - transformJSX?: (code: string) => string -} - /** * 页面运行时上下文。 */ From bad6a7d63b2b1880f1cb0df5ccf30c64a831de7b Mon Sep 17 00:00:00 2001 From: yy Date: Mon, 8 Jun 2026 10:05:30 +0800 Subject: [PATCH 3/3] refactor: replace shallowReactive with reactive in useState for enhanced state management --- renderer/state.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/renderer/state.ts b/renderer/state.ts index 4f7ff5e..000c74a 100644 --- a/renderer/state.ts +++ b/renderer/state.ts @@ -1,4 +1,4 @@ -import { shallowReactive } from 'vue' +import { reactive } from 'vue' import { parseData } from './render' import { getDeletedKeys, reset } from './dataUtils' import type { GetContext } from './types' @@ -14,7 +14,7 @@ interface UseStateOptions { * @param options - 配置项 */ export function useState({ getContext, registerStateAccessors }: UseStateOptions) { - const state = shallowReactive>({}) + const state = reactive>({}) /** * 将 schema.state 同步到运行时 state,并注册 accessor。