tmagic-editor/docs/runtime-api/data-source/dataSourceManager.md
roymondchen dfad691493 feat(data-source): 支持批量更新数据源
新增 dataSourceManager.batch(fn, options),fn 中的多次 setData 在结束后
统一更新一次页面:受影响节点只编译、替换一次,并按数据源分别触发 update-data。

- mergeChangeEvents 默认 false,每次变化各触发一次 update-data 并携带自己的
  changeEvent,不丢失中间变化;传 true 则每个数据源只触发一次
- 支持嵌套,仅支持同步函数;isBatching 暴露当前是否处于批量更新中
- 页面/页面片节点的替换改为批量 replaceChildNodes,避免每个节点全树搜索
- 数据源字段变更事件在 batch 内仍按每次 setData 触发
2026-10-30 16:47:56 +08:00

11 KiB
Raw Blame History

DataSourceManager

DataSourceManager 是 @tmagic/data-source 的核心类,用于管理所有数据源的生命周期、数据编译和事件分发。

构造函数

new DataSourceManager(options: DataSourceManagerOptions)

DataSourceManagerOptions

参数 类型 说明
app TMagicApp 应用实例
initialData DataSourceManagerData 初始数据(可选,用于 SSR)
useMock boolean 是否使用 Mock 数据(可选)

属性

属性 类型 说明
app TMagicApp 应用实例
dataSourceMap Map<string, DataSource> 数据源映射表
data DataSourceManagerData 所有数据源的数据
initialData DataSourceManagerData 初始化数据
useMock boolean 是否使用 Mock 数据
isMounted boolean 页面是否已渲染完成(收到过 mounted 事件)

静态方法

register

  • 参数:

    • {string} type 数据源类型
    • {typeof DataSource} dataSourceClass 数据源类
  • 返回:

    • {void}
  • 详情:

    注册自定义数据源类型。

  • 示例:

import { DataSourceManager } from '@tmagic/data-source';

class CustomDataSource extends DataSource {
  // 自定义实现
}

DataSourceManager.register('custom', CustomDataSource);

getDataSourceClass

  • 参数:

    • {string} type 数据源类型
  • 返回:

    • {typeof DataSource | undefined}
  • 详情:

    获取已注册的数据源类。

clearDataSourceClass

  • 返回:

    • {void}
  • 详情:

    清除所有已注册的数据源类(保留 base 和 http)。

registerObservedData

  • 参数:

    • {ObservedDataClass} observedDataClass 观察者数据类
  • 返回:

    • {void}
  • 详情:

    注册自定义的观察者数据类。

实例方法

init

  • 参数:

    • {DataSource} ds 单个数据源实例(必填)
  • 返回:

    • {Promise<void>}
  • 详情:

    初始化单个数据源。会按 methods 中 timing === 'beforeInit' 的方法依次执行,再调用 ds.init(),最后执行 timing === 'afterInit' 的方法。当 ds.isInit 为 true,或当前 app.jsEngine 命中 ds.schema.disabledInitInJsEngine 时直接跳过。

  • 示例:

const ds = dataSourceManager.get('ds_1');
if (ds) {
  await dataSourceManager.init(ds);
}

mounted

  • 参数:

    • {DataSource} ds 单个数据源实例(必填)
  • 返回:

    • {Promise<void>}
  • 详情:

    页面渲染完成后执行单个数据源的 mounted:先调用 ds.mounted(),再依次执行 methods 中 timing === 'mounted' 的方法。当 ds.isMounted 为 true,或当前 app.jsEngine 命中 ds.schema.disabledInitInJsEngine 时直接跳过。

    一般不需要手动调用,runtime 在顶层组件渲染完成后触发 mounted 事件,由 manager 统一对所有数据源调用该方法。

  • 示例:

// runtime 顶层组件渲染完成后
app.dataSourceManager?.emit('mounted');

get

  • 参数:

    • {string} id 数据源 ID
  • 返回:

    • {DataSource | undefined}
  • 详情:

    获取指定 ID 的数据源实例。

  • 示例:

const ds = dataSourceManager.get('ds_1');

addDataSource

  • 参数:

    • {DataSourceSchema} config 数据源配置(可选)
  • 返回:

    • {DataSource | undefined}
  • 详情:

    添加新的数据源。当对应类型尚未注册(即 dataSourceClassMap 中无该类型)时,会将配置缓存到 waitInitSchemaList,并返回 undefined,待 register 时再补建实例。

  • 示例:

const ds = dataSourceManager.addDataSource({
  id: 'new_ds',
  type: 'base',
  fields: []
});

removeDataSource

  • 参数:

    • {string} id 数据源 ID
  • 返回:

    • {void}
  • 详情:

    移除指定的数据源。

setData

  • 参数:

    • {DataSource} ds 数据源实例
    • {ChangeEvent} changeEvent 变化事件,形如 { updateData, path? }
  • 返回:

    • {void}
  • 详情:

    将 ds.data 同步到 this.data[ds.id],并以 (ds.id, changeEvent) 触发 change 事件。

updateSchema

  • 参数:

    • {DataSourceSchema[]} schemas 数据源配置数组
  • 返回:

    • {void}
  • 详情:

    同步更新数据源 DSL 配置:先按 id 移除已有数据源,再以 cloneDeep 重新 addDataSource,并对新建实例触发 init(异步执行,不会被该方法 await);若此时 isMounted 已为 true,init 完成后还会补充执行 mounted。一般在编辑器中修改配置后调用。

compiledNode

  • 参数:

    • {MNode} node 节点配置
    • {string} sourceId 数据源 ID(可选)
    • {boolean} deep 是否深度编译(可选)
  • 返回:

    • {MNode}
  • 详情:

    编译节点 DSL 中的数据源配置,将数据源引用替换为实际值。

  • 示例:

const compiledNode = dataSourceManager.compiledNode({
  id: 'text_1',
  type: 'text',
  text: '${ds_1.username}'
});
// compiledNode.text 会被替换为实际的用户名

compliedConds

  • 参数:

    • { { [NODE_CONDS_KEY]?: DisplayCond[]; [NODE_CONDS_RESULT_KEY]?: boolean; [NODE_DISABLE_DATA_SOURCE_KEY]?: boolean } } node 带条件字段的结构(不要求是完整的 MNode)
    • {DataSourceManagerData} data 数据上下文(可选,默认使用 this.data)
  • 返回:

    • {boolean} 节点是否应该显示
  • 详情:

    编译显示条件,返回节点是否应该显示。

  • 示例:

const shouldShow = dataSourceManager.compliedConds(node);

compliedIteratorItemConds

  • 参数:

    • {any} itemData 迭代项数据
    • { { [NODE_CONDS_KEY]?: DisplayCond[] } } node 带条件字段的结构
    • {string[]} dataSourceField 迭代数据在数据源中的字段路径,格式如 ['dsId', 'key1', 'key2'](可选,默认 [])
  • 返回:

    • {boolean}
  • 详情:

    编译迭代器项的显示条件。

compliedIteratorItems

  • 参数:

    • {any} itemData 迭代项数据
    • {MNode[]} nodes 节点配置数组
    • {string[]} dataSourceField 迭代数据在数据源中的字段路径(可选,默认 [])
  • 返回:

    • {MNode[]}
  • 详情:

    编译迭代器项的节点配置。

isAllDataSourceRegistered

  • 返回:

    • {boolean}
  • 详情:

    判断 DSL 中声明的所有数据源是否都已注册并实例化(即 dataSourceMap.size === dsl.dataSources.length,或 DSL 未声明数据源)。

    ::: tip registered-all 触发时机 registered-all 事件仅在 addDataSource 末尾、isAllDataSourceRegistered() 为真时触发。当 dsl.dataSources 为空数组时,构造函数不会进入任何一次 addDataSource 调用,因此不会触发该事件,但仍可能执行 callDsInit()。 :::

onDataChange

  • 参数:

    • {string} id 数据源 ID
    • {string} path 数据路径
    • {(payload: any) => void} callback 回调函数(具体入参取决于 ObservedData 实现,详见 DataSource.onDataChange)
    • { { immediate?: boolean } } options 选项(可选)
  • 返回:

    • {void}
  • 详情:

    监听数据源数据变化。options.immediate 为 true 时,订阅后立即用当前值触发一次回调(具体语义取决于 ObservedData 的实现)。

  • 示例:

dataSourceManager.onDataChange('ds_1', 'user.name', (newVal) => {
  console.log('用户名变更:', newVal);
});

offDataChange

  • 参数:

    • {string} id 数据源 ID
    • {string} path 数据路径
    • {(newVal: any) => void} callback 回调函数
  • 返回:

    • {void}
  • 详情:

    取消监听数据源数据变化。

batch

  • 参数:

    • {() => T} fn 同步执行的函数
    • {BatchOptions} [options] 批量更新选项,嵌套时以最外层为准
      • {boolean} [mergeChangeEvents=false] 是否把同一数据源的多次变化合并成一次 update-data
  • 返回:

    • {T} fn 的返回值
  • 详情:

    批量更新。fn 中产生的所有数据变化,会在 fn 执行结束后(同步)统一更新一次页面:受影响的节点只编译、替换一次,并按数据源分别触发 update-data。

    • 数据本身(data、onDataChange、change 事件)仍然实时生效,只是页面节点的更新被合并
    • 支持嵌套,最外层 batch 结束时才统一更新
    • 仅支持同步函数,fn 返回 Promise 时不会等待

    mergeChangeEvents 默认为 false:每次 setData 各触发一次 update-data,各自携带自己的 changeEvent(path、updateData),与不使用 batch 时一致,监听 update-data / replaced-node 的业务不会漏掉中间变化。页面节点只编译一次,且渲染层会按节点对象去重(同一份已编译节点只替换一次),因此事件变多不会带来重复的替换开销。

    传 true 时每个数据源只触发一次 update-data,changeEvent 是该数据源最后一次变化,中间变化会丢失;适合不关心每次变化、只想少收事件的场景。

    dataSourceManager.batch(() => {
      ds1.setData(list, 'list');
      ds1.setData(total, 'total');
      ds2.setData(title, 'title');
    });
    
    // 每个数据源只触发一次 update-data,只带最后一次 changeEvent
    dataSourceManager.batch(
      () => {
        ds1.setData(list, 'list');
        ds1.setData(total, 'total');
      },
      { mergeChangeEvents: true },
    );
    

isBatching

  • 类型: {boolean}

  • 详情:

    当前是否处于 batch 中(只读属性)。

destroy

  • 返回:

    • {void}
  • 详情:

    销毁数据源管理器,清理所有数据源。

事件

DataSourceManager 继承自 EventEmitter,支持以下事件:

事件名 说明 回调参数
change 单个数据源数据变化 (dsId: string, changeEvent: ChangeEvent)
init 所有数据源初始化完成;现代分支携带 (data, errors),旧 Promise.all 分支为 (this.data) (data, errors?)
mounted 由 runtime 在顶层组件渲染完成后触发,manager 收到后统一执行各数据源的 mounted 无
registered-all 所有数据源注册完成 无
batch-change batch 结束时发出,携带期间所有的数据变化,createDataSourceManager 据此统一更新页面 (changes: { sourceId: string, changeEvent: ChangeEvent }[])
update-data 由 createDataSourceManager 在数据变化后发出,用于通知节点重新渲染 (newNodes: MNode[], sourceId: string, changeEvent: ChangeEvent, pageId: Id)

事件监听示例

dataSourceManager.on('change', (dsId, changeEvent) => {
  console.log(`数据源 ${dsId} 变更:`, changeEvent);
});

dataSourceManager.on('init', (data, errors) => {
  console.log('所有数据源初始化完成', data, errors);
});