mirror of
https://github.com/Tencent/tmagic-editor.git
synced 2026-11-02 16:10:44 +00:00
新增 dataSourceManager.batch(fn, options),fn 中的多次 setData 在结束后 统一更新一次页面:受影响节点只编译、替换一次,并按数据源分别触发 update-data。 - mergeChangeEvents 默认 false,每次变化各触发一次 update-data 并携带自己的 changeEvent,不丢失中间变化;传 true 则每个数据源只触发一次 - 支持嵌套,仅支持同步函数;isBatching 暴露当前是否处于批量更新中 - 页面/页面片节点的替换改为批量 replaceChildNodes,避免每个节点全树搜索 - 数据源字段变更事件在 batch 内仍按每次 setData 触发
413 lines
11 KiB
Markdown
413 lines
11 KiB
Markdown
# DataSourceManager
|
||
|
||
`DataSourceManager` 是 `@tmagic/data-source` 的核心类,用于管理所有数据源的生命周期、数据编译和事件分发。
|
||
|
||
## 构造函数
|
||
|
||
```typescript
|
||
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}`
|
||
|
||
- **详情:**
|
||
|
||
注册自定义数据源类型。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
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` 时直接跳过。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
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 统一对所有数据源调用该方法。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
// runtime 顶层组件渲染完成后
|
||
app.dataSourceManager?.emit('mounted');
|
||
```
|
||
|
||
### get
|
||
|
||
- **参数:**
|
||
- `{string} id` 数据源 ID
|
||
|
||
- **返回:**
|
||
- `{DataSource | undefined}`
|
||
|
||
- **详情:**
|
||
|
||
获取指定 ID 的数据源实例。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
const ds = dataSourceManager.get('ds_1');
|
||
```
|
||
|
||
### addDataSource
|
||
|
||
- **参数:**
|
||
- `{DataSourceSchema} config` 数据源配置(可选)
|
||
|
||
- **返回:**
|
||
- `{DataSource | undefined}`
|
||
|
||
- **详情:**
|
||
|
||
添加新的数据源。当对应类型尚未注册(即 `dataSourceClassMap` 中无该类型)时,会将配置缓存到 `waitInitSchemaList`,并返回 `undefined`,待 `register` 时再补建实例。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
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 中的数据源配置,将数据源引用替换为实际值。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
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}` 节点是否应该显示
|
||
|
||
- **详情:**
|
||
|
||
编译显示条件,返回节点是否应该显示。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
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](./dataSource.md#ondatachange))
|
||
- `{ { immediate?: boolean } } options` 选项(可选)
|
||
|
||
- **返回:**
|
||
- `{void}`
|
||
|
||
- **详情:**
|
||
|
||
监听数据源数据变化。`options.immediate` 为 `true` 时,订阅后立即用当前值触发一次回调(具体语义取决于 `ObservedData` 的实现)。
|
||
|
||
- **示例:**
|
||
|
||
```typescript
|
||
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` 是该数据源最后一次变化,中间变化会丢失;适合不关心每次变化、只想少收事件的场景。
|
||
|
||
```typescript
|
||
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)` |
|
||
|
||
### 事件监听示例
|
||
|
||
```typescript
|
||
dataSourceManager.on('change', (dsId, changeEvent) => {
|
||
console.log(`数据源 ${dsId} 变更:`, changeEvent);
|
||
});
|
||
|
||
dataSourceManager.on('init', (data, errors) => {
|
||
console.log('所有数据源初始化完成', data, errors);
|
||
});
|
||
```
|