Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 14 additions & 8 deletions renderer/RenderMain.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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()
Expand All @@ -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

Expand Down Expand Up @@ -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) => {
Expand All @@ -109,6 +111,9 @@ export default {
if (!data || !Object.keys(data).length) {
return
}

clearStateAccessors()

const newSchema = JSON.parse(JSON.stringify(data))
const context = {
state,
Expand All @@ -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()
Expand All @@ -144,6 +149,7 @@ export default {
}

onUnmounted(async () => {
clearStateAccessors()
await invokePageOnUnmounted()
})

Expand Down
101 changes: 101 additions & 0 deletions renderer/accessor.ts
Original file line number Diff line number Diff line change
@@ -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<string, WatchStopHandle>()

/**
* 解析 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<string, StateAccessorConfig | unknown>): 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
}
}
24 changes: 24 additions & 0 deletions renderer/dataUtils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* 清空对象上的所有可枚举属性。
*
* @param obj - 待清空的对象
*/
export const reset = (obj: Record<string, unknown>): void => {
Object.keys(obj).forEach((key) => delete obj[key])
}

/**
* 对比两个对象,返回 objA 中存在但 objB 中不存在的 key 列表。
*
* @param objA - 当前运行时对象
* @param objB - 新的 schema 配置对象
* @returns 需要从运行时对象中删除的 key
*/
export const getDeletedKeys = (
objA: Record<string, unknown> | null | undefined,
objB: Record<string, unknown> | null | undefined
): string[] => {
const keyB = new Set(Object.keys(objB || {}))

return Object.keys(objA || {}).filter((item) => !keyB.has(item))
}
47 changes: 47 additions & 0 deletions renderer/state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { reactive } from 'vue'
import { parseData } from './render'
import { getDeletedKeys, reset } from './dataUtils'
import type { GetContext } from './types'

interface UseStateOptions {
getContext: GetContext
registerStateAccessors: (data: Record<string, unknown>) => void
}

/**
* 创建页面级 state 管理器。
*
* @param options - 配置项
*/
export function useState({ getContext, registerStateAccessors }: UseStateOptions) {
const state = reactive<Record<string, unknown>>({})

/**
* 将 schema.state 同步到运行时 state,并注册 accessor。
*
* @param data - schema.state 原始配置
* @param clear - 是否全量清空后再写入(外部 API 兼容)
*/
const setState = (data: Record<string, unknown>, 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
}
}
28 changes: 28 additions & 0 deletions renderer/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
/**
* Schema 协议中的 JSFunction 描述符。
*/
export interface JSFunctionDescriptor {
type: 'JSFunction'
value: string
}

/**
* 带 getter/setter 的状态访问器配置。
*/
export interface StateAccessorConfig {
accessor?: {
getter?: JSFunctionDescriptor
setter?: JSFunctionDescriptor
}
defaultValue?: unknown
}

/**
* 页面运行时上下文。
*/
export type PageContext = Record<string, unknown>

/**
* 获取页面运行时上下文的函数。
*/
export type GetContext = () => PageContext