十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

GrapesJS DataSource API 完整解析:数据源管理、记录操作与关系解析实战

GrapesJS DataSource API 完整解析:数据源管理、记录操作与关系解析实战 GrapesJS DataSource API 完整解析数据源管理、记录操作与关系解析实战【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs导读DataSource数据源是 GrapesJS 无代码 Web 构建框架中用于集中管理动态数据的核心模型类它持有记录集合records、字段模式schema与可选的远程 Provider并通过 transformers 在数据写入、读取和删除阶段进行校验与转换。本文基于当前仓库中 docs/api/datasource.md 的 API 说明结合 DataSource.ts 的源码实现与测试用例系统讲解数据源的构造、记录增删查改、Schema 更新与关系解析、Provider 远程加载以及如何在编辑器中通过editor.DataSources模块使用它们。读完本文你将能独立完成静态记录数据源 动态远程数据源 跨数据源关系解析 实时 UI 同步的完整数据驱动页面开发。DataSource 是什么职责与核心属性从源码注释与类定义看DataSource继承自 Backbone 风格的Model代表编辑器内一个可被全局引用的数据源。它管理一组数据记录DataRecord并提供与记录交互的方法同时可通过 transformers 在 add写入、read读取、delete删除阶段修改记录。源码入口一个数据源在内部由以下属性构成见 types.tsid数据源唯一标识也是所有路径寻址如my-datasource.id1.content的第一段。recordsDataRecords集合容纳若干DataRecord。transformers转换器对象目前核心支持onRecordSetValue钩子。schema字段模式用于描述字段类型并支撑关系字段解析。provider可选远程数据提供者字符串 URL 或{ get: {...} }配置。skipFromStorage为true时不写入项目 JSON。defaults()方法DataSource.ts给出默认属性records为空数组、transformers为空对象schema 在构造时默认初始化为{}见 DataSource.ts。构造 DataSource从 props 到实例官方文档给出的基础用法const dataSource new DataSource({ records: [ { id: id1, name: value1 }, { id: id2, name: value2 } ], }, { em: editor }); dataSource.addRecord({ id: id3, name: value3 });构造函数接收两个参数propsDataSourcePropsDRProps初始化属性至少应包含id与records还可携带schema、provider、transformers、skipFromStorage。optsDataSourceOptions初始化选项其中em指向编辑器模型EditorModel使数据源能与编辑器事件系统、UndoManager 联动。构造时的关键逻辑DataSource.ts先把schema合并为{}、records置空数组后调用父类Model构造从 props 中取出transformers并挂载到实例若传入的records不是DataRecords实例则包装为new DataRecords(records, { dataSource: this })并set回去——因此无论传入的是纯对象数组还是集合最终dataSource.records都是DataRecords集合监听集合的add与collectionEventsadd时触发新记录的 change 事件onAdd集合变化时刷新上下文缓存并上报编辑器变更handleChanges调用em.DataSources.invalidateContextCache()与em.changesUp。实际开发中推荐用法通过编辑器模块创建在真实应用中数据源通常通过editor.DataSources模块添加而非直接new DataSourceconst editor grapesjs.init({ container: #gjs }); const datasource { id: my-datasource, records: [ { id: id1, content: Hello World }, { id: id2, color: red }, ], }; editor.DataSources.add(datasource);DataSourceManager.add()index.ts在无id时会自动生成并委托给内部all集合DataSources完成实例化。记录集合DataRecords 与 getRecord 的寻址规则records访问器DataSource.ts返回DataRecords集合。该集合除继承 Collection 的常规能力外还实现了特殊的寻址逻辑DataRecords.tsgetRecord(id)先按id精确查找找不到时若id是合法的非负整数字符串或数字均可则回退为按索引查找getRecordByIndex。isIndexKey(id)判断传入的 id 是否为索引键即不存在同名 id、但可用作索引。这意味着ds.getRecord(0)与ds.getRecord(0)都可以定位到集合中第 0 条记录方便在不知道记录 id、只知道顺序的场景下取值。每条DataRecord还提供路径能力DataRecord.tsrecord.getPath(); // SOURCE_ID.record1 record.getPath(myProp); // SOURCE_ID.record1.myProp record.getPaths(); // [SOURCE_ID.record1, SOURCE_ID.0]getPaths同时返回基于 id 的路径与基于索引的路径这正是组件、样式、Trait 中dataResolver.path寻址的底层依据也是data:path事件广播的基础。记录增删查改五大核心方法addRecord新增记录addRecord(record: DRProps, opts?: AddOptions): DataRecord直接委托给this.records.add(record, opts)DataSource.ts返回新加入的DataRecord。新增后会自动触发集合的add事件onAdd会对记录调用triggerChange()从而推动依赖该数据源的 UI 刷新。getRecord按 ID 查询getRecord(id: string | number): DataRecord | undefined委托this.records.getRecord(id)支持 id 与索引两种寻址见上文查不到时返回undefined。getRecords读取全部经过读转换getRecords(): ArrayDataRecord | undefined实现为[...this.records.models].map((record) this.getRecord(record.id)!)DataSource.ts。文档特别强调每条记录都会经过getRecord处理以应用读取阶段的 transformers因此返回的是处理后的记录集合快照。removeRecord按 ID 删除removeRecord(id: string | number, opts?: RemoveOptions): DataRecord | undefined删除前先做可变性校验DataSource.tsconst record this.getRecord(id); if (record?.mutable false !opts?.dangerously) { throw new Error(Cannot remove immutable record); } return this.records.remove(id, opts);即不可变记录默认无法删除除非显式传入{ dangerously: true }。setRecords整体替换setRecords(records: DRProps[]): DataRecord[]先this.records.reset([], { silent: true })静默清空再逐条addDataSource.ts。这是 Provider 远程加载、批量导入等场景的入口。记录可变性mutable记录默认是可变的创建时指定mutable: false可锁定记录DataRecord.ts。锁定的影响有两处set()修改属性时抛错Cannot modify immutable recordDataRecord.tsremoveRecord()删除时抛错Cannot remove immutable record。const dataSource { id: my-datasource, records: [ { id: id1, content: Mutable content }, { id: id2, content: Immutable content, mutable: false }, ], }; editor.DataSources.add(dataSource); const ds editor.DataSources.get(my-datasource); ds.getRecord(id1).set(content, Updated content); // 成功 ds.getRecord(id2).set(content, New content); // 抛错该行为在 Record Mutability 一节 有完整示例源码校验逻辑见 DataRecord.ts。Schema 管理upSchema 与 getSchemaFieldDataSource支持通过 schema 描述记录字段用于类型约束与关系解析。upSchema增量更新模式upSchema(schema: Partialtypeof this.schema, opts?: SetOptions)实现为浅合并this.set(schema, { ...this.schema, ...schema }, opts)DataSource.ts即只更新传入的字段保留其余字段。dataSource.upSchema({ name: { type: string } });getSchemaField读取字段定义getSchemaField(fieldKey: keyof DRProps)直接返回this.schema[fieldKey]const fieldSchema dataSource.getSchemaField(name); fieldSchema.type; // stringSchema 的字段类型体系类型定义见 types.tsDataFieldPrimitiveType枚举包含类型含义附加字段string字符串可选enum: string[]枚举约束number数字—boolean布尔—date日期—json任意 JSON—relation关系字段target目标数据源 id、targetField?目标字段默认id、isMany?是否一对多所有字段类型都继承基础字段能力default、description、labelDataFieldSchemaBase。getResolvedRecords关系字段解析hasProvider之外最值得一提的能力是关系解析DataSource.ts遍历this.schema对每个type relation且带target的字段通过this.em.DataSources.get(fieldSchema.target)找到目标数据源以targetField || id默认DEF_DATA_FIELD_ID id见 utils.ts作为关联键isMany为真时把关联值数组或单值映射为多条目标记录数组否则解析为单条记录对象返回的新数组中关系字段被替换为目标记录的完整属性副本。测试用例 DataSource.ts 验证了该行为blogs数据源的author一对一到users与categories一对多到categories字段均被解析为内嵌对象/数组。Provider让数据源从远程加载hasProvider属性DataSource.ts判断数据源是否配置了provider。loadProvider()DataSource.ts执行远程加载流程无provider直接返回字符串形式的 provider 在源码中标记为 TODO后续以插件方式实现解析provider.get可简写为字符串 URL或{ url, method, headers, body }对象后调用fetch触发data:provider:load:before事件请求成功且响应包含records时调用setRecords包含schema时调用upSchema触发data:provider:load事件回调载荷为{ result, dataSource }失败时记录错误日志并触发data:provider:load:error。editor.DataSources.add({ id: blogs, name: My blogs, provider: { get: { url: https://api.example.com/data, method: GET, headers: { Content-Type: application/json }, }, }, }); const ds editor.DataSources.get(blogs); await ds.loadProvider();测试 DataSource.ts 验证了三件事加载后 schema 与记录均被更新且关系可正常解析fetch 失败时 schema 保持{}、记录数为 0且会走错误事件Provider 拉取的记录不会被持久化——store()时对带 provider 的数据源不写入 records只保留 id、schema 与 provider 配置见 index.ts。在项目加载时若开启配置autoloadProviders: true默认false见 config.tsload()会并行加载所有带 provider 的数据源并依次触发data:provider:loadAll:before与data:provider:loadAllindex.ts。完整行为由测试 load providers on project load 覆盖DataSource.ts。在编辑器中管理数据源DataSources 模块 APIeditor.DataSourcesDataSourceManager是对外的主要入口其 API 文档见 docs/api/datasources.md核心方法如下方法说明示例add(props, opts?)新增数据源缺 id 自动生成dsm.add({ id: ds, records: [...] })get(id)按 id 获取数据源dsm.get(my_data_source_id)getAll()返回所有数据源数组dsm.getAll()remove(id, opts?)移除数据源dsm.remove(DS_ID)getValue(path, defValue?)按路径取记录值dsm.getValue(ds_id.record_id.propName, default)setValue(path, value)按路径写记录值成功返回truedsm.setValue(ds_id.record_id.propName, new value)fromPath(path)解析路径为[dataSource, dataRecord, propPath]dsm.fromPath(ds_id.record_id.myProp)store()/load(data)项目 JSON 序列化 / 反序列化—其中getValue/setValue基于stringToPath拆解数据源id.记录id.属性路径三段定位index.tsfromPath的实现index.ts同样支持resPath拼接返回嵌套属性路径。事件系统让数据驱动的 UI 实时同步数据源与记录的变化通过编辑器事件广播事件名定义在DataSourcesEvents枚举types.ts事件触发时机回调载荷data:add/data:add:before数据源添加(dataSource)data:remove/data:remove:before数据源移除(dataSource)data:update数据源更新(dataSource, changes)data:path记录任意路径更新({ dataSource, dataRecord, path, options })data:path:SOURCE_ID.RECORD_ID.PROP指定路径更新同上path 为该路径data:pathSource:SOURCE_ID按数据源过滤的路径更新同上data:provider:load/:before/:errorProvider 加载各阶段({ dataSource, result })等data:provider:loadAll/:before项目加载时批量加载 Provider()data上述所有事件的汇总事件({ event, model, ... })data:path系列事件的触发源头在DataRecord.triggerChange()DataRecord.ts它会把 id 路径与索引路径都广播出去——这正是修改记录属性 → 画布上绑定组件自动更新的同步链路核心。editor.on(data:path:MY_DS.ID1.CONTENT, ({ dataSource, dataRecord, path }) { console.log(指定路径更新, path); }); editor.on(data:path, ({ dataSource, dataRecord, path }) { console.log(任意数据源路径更新); });与组件、样式、Trait 的数据绑定数据源的最终价值在于被组件消费。组件内通过data-variable类型组件和dataResolver.path引用数据editor.addComponents([ { tagName: h1, type: text, components: [ { type: data-variable, dataResolver: { defaultValue: default, path: my-datasource.id1.content, }, }, ], style: { color: { type: data-variable, defaultValue: red, path: my-datasource.id2.color, }, }, }, ]);Trait 同样支持绑定editor.addComponents([ { tagName: input, traits: [ name, type, { type: text, label: Value, name: value, value: { type: data-variable, defaultValue: default, path: my-datasource.id1.value, }, }, ], }, ]);完整示例参见 docs/modules/DataSources.md 的 Components、Styles、Traits 三节。配合计数器示例docs/modules/DataSources.md 中 counter 例子可以看到数据驱动 UI 的完整闭环const ds editor.DataSources.get(my-datasource); setInterval(() { const counterRecord ds.getRecord(id1); counterRecord.set({ counter: counterRecord.get(counter) 1 }); }, 1000);每次set都会触发data:path事件链画布中绑定该路径的文本组件随之刷新实现了数据模型 → 编辑器事件 → 组件 UI的实时同步。持久化skipFromStorage 与项目 JSONstore()index.ts遍历所有数据源跳过skipFromStorage: true的项对普通数据源写入id、非空schema以及无 provider 时的records。因此const persistentDataSource { id: persistent-datasource, records: [{ id: id1, content: This data will be saved }], }; editor.DataSources.add(persistentDataSource); const temporaryDataSource { id: temporary-datasource, records: [{ id: id1, content: This data will not be saved }], skipFromStorage: true, }; editor.DataSources.add(temporaryDataSource);项目加载时自动恢复已保存的数据源但skipFromStorage的数据源不会自动出现需以代码重新添加。详情见 docs/modules/DataSources.md 的 Storing DataSources in Project JSON 一节。模块配置项DataSourceManager提供两个配置config.ts通过editor.getConfig()的dataSources键或初始化时传入配置默认值说明autoloadProvidersfalse项目加载时是否自动加载所有带 provider 的数据源dataBindingImportPolicyoverwrite字符串导入如resetFromString、Css.addCollection与已有数据绑定的冲突策略overwrite覆盖绑定、skip保留绑定、update写回数据源并保留绑定、或传入(context) action按字段决策const editor grapesjs.init({ container: #gjs, dataSources: { autoloadProviders: true, dataBindingImportPolicy: skip, }, });综合实战博客系统的数据源设计结合以上 API设计一个博客 用户 分类的完整数据源方案参照测试 DataSource.ts 中的结构与数据const editor grapesjs.init({ container: #gjs }); // 1. 静态数据源用户与分类 editor.DataSources.add({ id: users, records: [ { id: u1, name: Alice }, { id: u2, name: Bob }, ], }); editor.DataSources.add({ id: categories, records: [ { id: c1, name: Tech }, { id: c2, name: Life }, { id: c3, name: News }, ], }); // 2. 远程数据源博客关系字段指向 users / categories const blogs editor.DataSources.add({ id: blogs, name: My blogs, schema: { author: { type: relation, target: users, targetField: id }, categories: { type: relation, target: categories, targetField: id, isMany: true }, }, provider: { get: { url: https://api.example.com/blogs, headers: { Content-Type: application/json }, }, }, }); // 3. 手动加载 Provider并消费解析后的关系数据 await blogs.loadProvider(); const resolved blogs.getResolvedRecords(); // resolved[0].author { id: u1, name: Alice } // resolved[0].categories [{ id: c1, name: Tech }, ...] // 4. 记录增删查改 blogs.addRecord({ id: b4, title: New Post, author: u2, categories: [c3] }); const record blogs.getRecord(b4); blogs.setRecords([{ id: b5, title: Replace all }]); // 整体替换 blogs.removeRecord(b5); // 5. 在组件中绑定动态数据 editor.addComponents([ { tagName: h1, type: text, components: [{ type: data-variable, dataResolver: { defaultValue: Loading..., path: blogs.b1.title }, }], }, ]);小结DataSource是 GrapesJS 数据驱动构建能力的基石addRecord/getRecord/getRecords/removeRecord/setRecords构成记录生命周期管理upSchema/getSchemaField/getResolvedRecords提供字段描述与跨数据源关系解析hasProvider/loadProvider打通远程数据通道mutable与skipFromStorage分别守护数据完整性与存储边界而data:path等事件体系则保证了改数据 → UI 同步的实时联动。想要进一步深挖可以继续阅读 DataSource.ts、DataRecord.ts、DataSources 模块文档 以及配套测试 DataSource.ts并参考 DataSources 使用指南 中的组件、样式、Trait 绑定示例进行实践。【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表