-
Notifications
You must be signed in to change notification settings - Fork 2
feat: implement state accessor management #20
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
yy-wow
wants to merge
3
commits into
poc/dynamic-ui
Choose a base branch
from
feat/support-accessor
base: poc/dynamic-ui
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
+214
−8
Open
Changes from 1 commit
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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)) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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<string, unknown>) => void | ||
| } | ||
|
|
||
| /** | ||
| * 创建页面级 state 管理器。 | ||
| * | ||
| * @param options - 配置项 | ||
| */ | ||
| export function useState({ getContext, registerStateAccessors }: UseStateOptions) { | ||
| const state = shallowReactive<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 | ||
| } | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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 { | ||
|
yy-wow marked this conversation as resolved.
Outdated
|
||
| Function?: FunctionConstructor | ||
| transformJSX?: (code: string) => string | ||
| } | ||
|
|
||
| /** | ||
| * 页面运行时上下文。 | ||
| */ | ||
| export type PageContext = Record<string, unknown> | ||
|
|
||
| /** | ||
| * 获取页面运行时上下文的函数。 | ||
| */ | ||
| export type GetContext = () => PageContext | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.