GrapesJS Component API 完全指南:掌握模板节点模型的核心能力
【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs
导读
Component(组件)是 GrapesJS 模板结构的核心节点对象,理解它的 API 是进行二次开发、自定义组件类型与操控画布内容的基础。本文以docs/api/component.md为骨架,结合packages/core/src/dom_components/model/Component.ts的源码实现,系统讲解 Component 的全部属性、生命周期钩子与常用方法,帮助你在编辑器实例中完成对节点树的高效增删改查、样式与属性管理、Traits 配置及 HTML 导出等实战操作。
Component 是什么:模板树中的单一节点
在 GrapesJS 中,整个模板(Template)本质上是一棵由 Component 节点组成的树:页面中的每个元素——无论是文本、图片、视频还是自定义组件——都被抽象为一个 Component 对象。文档开篇即点明其核心定位:
The Component object represents a single node of our template structure, so when you update its properties the changes are immediately reflected on the canvas and in the code to export.
这意味着更新 Component 的属性(Properties)后,改动会立即同步反映到画布与导出代码中——GrapesJS 在导出代码时就是递归遍历整棵节点树来生成 HTML。因此,掌握 Component 的属性读写方式,就掌握了操控编辑器的"遥控器"。
最基本的读写方式是set/get:
component.set({ tagName: 'span', attributes: { title: 'Hello' }, removable: false, }); component.get('tagName'); // -> 'span'在源码 Component.ts 中,Component类继承自StyleableModel(一个可样式化、可序列化的 Backbone 风格模型),并通过defaultsgetter 定义了全部内置属性的默认值。type、tagName、attributes、traits等属性都存储于模型属性中,这也是set/get能直接操作它们的原因。
属性详解:从type到delegate
以下属性是 Component 模型的内置配置项,可以在组件定义、set()调用或自定义组件类型中直接使用。本节完整覆盖文档中的属性清单,并补充源码默认值与内部实现要点。
标识与渲染
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
type | String | '' | 组件类型,如text、image、video等,决定使用哪个已注册的组件类型模型 |
tagName | String | 'div' | 组件的 HTML 标签,如span |
attributes | Object | {} | 组件的属性键值对,如{ title: 'Hello' } |
name | String | '' | 组件名称,用于 Layers 面板与画布上的 badge 徽标显示 |
icon | String | '' | 组件图标字符串,插入在名称之前(Layers 与 badge 中),可为 HTML 字符串如<i class="fa fa-square-o"></i> |
void | Boolean | false | HTML 导出器使用:void 元素没有闭合标签,如<br/>、<hr/> |
void属性的实际效果可在 toHTML 实现 中看到:当没有内部内容且void为真时,输出自闭合形式的标签(skipEndTag逻辑)。
交互与可操作性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
removable | Boolean | true | 为false时组件不可从画布移除 |
draggable | Boolean \| String \| Function | true | 是否可被拖入其他组件内部。可传 CSS 查询串限定可拖入的目标,如'.some-class[title=Hello], [data-gjs-type=column]'(仅可拖入含some-class类与Hellotitle 的元素及column类型组件);传函数时,目标(target)与目的地(destination)组件作为参数传入,返回布尔值决定是否允许拖拽 |
droppable | Boolean \| String \| Function | true | 是否允许其他组件放入自身内部,查询串/函数用法与draggable一致 |
badgable | Boolean | true | 设为false则不在组件上方显示 badge(含名称的徽标) |
highlightable | Boolean | true | 为true时可用虚线边框高亮 |
copyable | Boolean | true | 是否允许克隆组件 |
resizable | Boolean \| Object | false | 是否可调整组件大小;也可传对象作为 Resizer 的选项 |
editable | Boolean | false | 是否允许编辑组件内容(用于 Text 组件) |
layerable | Boolean | true | 为false时组件在 Layers 面板中隐藏 |
selectable | Boolean | true | 点击时是否允许被选中 |
hoverable | Boolean | true | 为true时悬停元素显示高亮轮廓 |
locked | Boolean | undefined | 禁用画布中组件及其子级的选中;可将子级locked设为false单独解锁 |
draggable、copyable、removable三个属性不仅控制交互行为,还直接影响选中组件时工具栏(Toolbar)中自动生成的按钮。在 initToolbar 方法 中可以看到:当toolbar属性为假值时,编辑器会根据draggable添加tlb-move、根据copyable添加tlb-clone、根据removable添加tlb-delete,存在父级时还会添加core:component-exit(选中父组件)按钮。
样式与内容
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
stylable | Boolean \| Array<String> | true | 是否可对组件设置样式;传数组(如['color', 'width'])时表示只有这些 CSS 属性在 Style Manager 中可见 |
stylable-require | Array<String> | [] | 显示被标记为toRequire的样式属性数组 |
unstylable | Array<String> | [] | 在 Style Manager 中隐藏的样式属性数组 |
style | Object | '' | 组件默认样式,如{ width: '100px', height: '100px', 'background-color': 'red' } |
styles | String | '' | 组件相关样式,如.my-component-class { color: red } |
content | String | '' | 组件内容(不转义),在子组件渲染前追加 |
脚本与数据
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
script | String \| Function | '' | 组件的 JavaScript,详见 Components-js 模块 |
script-export | String \| Function | '' | 仅用于导出函数(如获取 HTML 时)的 JavaScript;定义了它会在导出时覆盖script |
traits | Array<Object \| String> | ['id', 'title'] | 组件的 Traits 特性配置,详见 Traits 模块 |
propagate | Array<String> | [] | 指定会被所有新追加的子组件继承的属性数组。例如{ removable: false, draggable: false, propagate: ['removable', 'draggable'] }后追加的新子组件会获得完全相同的属性值(包括propagate自身) |
components | Collection<Component> | null | 子组件集合 |
工具栏与委托
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
toolbar | Array<Object> | null | 选中组件时工具栏中显示的自定义项目数组,如toolbar: [ { attributes: {class: 'fa fa-arrows'}, command: 'tlb-move' }, ... ]。为假值时编辑器自动添加core:component-exit(有父组件时)、tlb-move(draggable时)、tlb-clone(copyable时)、tlb-delete(removable时) |
delegate | Object | null | 将命令委托给其他组件。可用命令:remove|move|copy|select,如{ remove: (cmp) => cmp.closestType('other-type') } |
生命周期钩子:init / updated / removed
Component 模型提供三个可覆写的生命周期钩子,用于在组件模型的关键节点执行自定义逻辑:
init()—— 模型创建时调用一次。自定义组件类型时常用它来初始化内部状态、绑定事件或设置默认子结构。updated(property, value, previous)—— 模型被更新时调用(如更新了某个属性)。参数:property(属性名)、value(属性新值)、previous(属性旧值),仅在属性更新后触发时传入。removed()—— 模型被移除后调用一次,可用于清理外部引用或释放资源。
这些钩子通常结合editor.Components.addType()在自定义组件类型的model定义中覆写使用。
类型判断与树形检索
Component 提供了一套完整的类型判断与节点检索 API,覆盖"渲染前/渲染后"两种场景。
类型判断
component.is('image'); // -> false(若当前组件不是 image 类型)源码中 is 方法 直接比较this.get('type')与传入类型。
isInstanceOf(type)检查组件是否属于某个组件类型的实例,支持继承链判断。源码 isInstanceOf 通过instanceof与typeExtends集合处理多级继承:
// 通过扩展现有类型新增一个组件类型 editor.Components.addType('text-ext', { extend: 'text' }); const newTextExt = editor.getSelected().append({ type: 'text-ext' })[0]; newTextExt.isInstanceOf('text-ext'); // true newTextExt.isInstanceOf('text'); // true(继承自 text)isChildOf(component)检查组件是否为某个组件(或某组件类型)的子级。传入字符串时按组件类型匹配:
const newTextComponent = editor.getSelected().append({ type: 'text', components: 'My text <b>here</b>', })[0]; const innerComponent = newTextComponent.find('b')[0]; innerComponent.isChildOf(newTextComponent); // true innerComponent.isChildOf('text'); // true其实现(源码)会沿parent()链向上遍历判断。
向下检索:find / findType / findFirstType
find(query)—— 通过 CSS 查询串查找内部组件,返回组件数组:
component.find('div > .class'); // -> [Component, Component, ...]注意:文档明确警告,find仅对已渲染的组件生效(实现依赖this.view.$el.find(query),见 源码)。
findType(query, opts)—— 按组件类型查找所有内部组件。与find相比,它不依赖渲染状态,在组件渲染前即可使用,是推荐方式:
const allImages = component.findType('image'); console.log(allImages[0]); // 第一个匹配组件 // 也支持函数匹配器,并通过 opts.max 限制匹配数量 const someComponents = component.findType((cmp) => cmp.getType() === 'something', { max: 2 });源码实现(findType)使用getComponentMatcher将字符串/函数统一为匹配函数,并深度优先递归遍历components()集合,命中max上限后提前退出。
findFirstType(query)—— 查找第一个匹配的组件,无匹配时返回undefined:
const image = component.findFirstType('image'); if (image) { console.log(image); } const firstImage = component.findFirstType((cmp) => cmp.is('image'));其实现即findType(query, { max: 1 }).at(0)(见 源码)。
向上检索:closest / closestType / contains
closest(query)—— 按查询串查找最近的父级组件(同样仅适用于已渲染的组件):
component.closest('div.some-class'); // -> ComponentclosestType(query)—— 按类型查找最近父级组件,不依赖渲染状态。实现(源码)从parent()起沿链向上,直到匹配器返回真值:
const Section = component.closestType('section'); console.log(Section); const namedSection = component.closestType((cmp) => cmp.getName() === 'Section');contains(component)—— 返回传入组件是否为当前组件的后代:
const isDescendant = component.contains(otherComponent); // Boolean属性操作:attributes、style 与 classes
attributes 三件套
setAttributes(attrs, opts)—— 更新组件属性(整体替换 attributes 对象):
component.setAttributes({ id: 'test', 'data-key': 'value' });addAttributes(attrs, opts)—— 在现有属性上追加新键值(内部会先读取当前属性再合并,见 源码):
component.addAttributes({ 'data-key': 'value' });removeAttributes(attrs, opts)—— 移除单个或多个属性:
component.removeAttributes('some-attr'); component.removeAttributes(['some-attr1', 'some-attr2']);三者均返回this便于链式调用。底层 setAttributes 通过this.set('attributes', { ...attrs }, opts)完成模型更新,opts支持SetAttrOptions(默认{ skipWatcherUpdates: false, fromDataSource: false },用于数据源绑定场景)。
getAttributes(opts)—— 返回组件全部属性。opts支持{ noClass: boolean, noStyle: boolean, skipResolve: boolean },分别用于排除 class、style 以及跳过数据解析。
style 读写
getStyle(opts)—— 获取组件样式对象。实现(源码)中有一个关键行为:当编辑器配置了avoidInline(避免内联样式)时,会改为从 CSS 规则(em.Css.getIdRule)中读取样式而非内联 style。
setStyle(prop, opts)—— 设置组件样式:
component.setStyle({ color: 'red' });class 管理
四个方法覆盖了 class 的增、改、删、查,均接受字符串或数组(支持空格分隔的字符串):
model.addClass('class1'); model.addClass('class1 class2'); model.addClass(['class1', 'class2']); // -> [SelectorObject, ...](返回新增的 Selector 数组) model.setClass('class1 class2'); // 重置当前 class 集合 model.removeClass(['class1', 'class2']); // 移除后返回被移除的 Selector 数组 component.getClasses(); // -> ['class1', 'class2'](字符串数组)setClass会重置当前集合后添加(源码 setClass),适用于整体替换。
子组件树管理
append 与 components
append(components, opts)—— 追加子组件,接受 Component、HTML 字符串或二者数组,返回追加后的组件数组:
someComponent.get('components').length; // -> 0 const videoComponent = someComponent.append('<video></video><div></div>')[0]; // 向 someComponent 添加了 2 个组件(video 和 div) someComponent.get('components').length; // -> 2 // 也可以直接传入组件对象 otherComponent.append(otherComponent2); otherComponent.append([otherComponent3, otherComponent4]); // 指定插入位置(如开头) someComponent.append(otherComponent, { at: 0 });components()—— 双用途方法:无参时返回当前子组件集合;传参时重置集合并追加新内容:
// 设置新集合 component.components('<span></span><div></div>'); // 获取当前集合 const collection = component.components(); console.log(collection.length); // -> 2其源码实现(components)先coll.reset(undefined, opts)清空再append。
定位与清空
// 返回指定索引的子组件(不存在则返回 null/undefined) component.getChildAt(0); // 第一个子组件 component.getChildAt(1); // 第二个子组件 // 返回最后一个子组件 const lastChild = component.getLastChild(); // 清空所有内部组件 component.empty(); // 返回 this父级访问
component.parent(); // -> Component 或 null component.parents(); // -> [Component, Component, ...](从直接父级到根的所有祖先)parents 实现 递归拼接父级链;parent(opts)支持传{ prev: true }读取移动前的原父级(prevColl)。
replaceWith / remove / move
replaceWith(el, opts)—— 用其他组件或 HTML 字符串替换当前组件:
const result = component.replaceWith('<div>Some new content</div>'); // result -> [Component](替换产生的新组件数组)实现(源码)先记录当前位置at,移除自身后在原位置插入新组件。
remove(opts)—— 从画布/树中移除组件,返回this。
move(component, opts)—— 将当前组件移动到目标组件内部(作为其子级):
// 把当前选中组件移动到 wrapper 顶部 const dest = editor.getWrapper(); editor.getSelected().move(dest, { at: 0 });实现(源码)会智能处理同父级内的索引位移(sameParent且目标索引大于当前索引时at减 1),并通过临时移除(temporary: 1)+ 追加完成移动,保证撤销栈记录正确。
Traits 特性管理
Traits 是组件在属性面板(Trait Manager)中暴露的可配置项,默认值为['id', 'title']。Component 提供了一套完整的 Traits 操作 API:
getTraits()—— 返回当前 Traits 数组:
const traits = component.getTraits(); // [Trait, Trait, Trait, ...]setTraits(traits)—— 用新的定义数组整体替换 Traits 集合:
const traits = component.setTraits([{ type: 'checkbox', name: 'disabled' }, ...]); // [Trait, ...]getTrait(id)—— 按 id/name 获取单个 Trait,未找到返回null:
const traitTitle = component.getTrait('title'); traitTitle && traitTitle.set('label', 'New label');updateTrait(id, props)—— 更新某个 Trait 的属性(如动态切换类型与选项):
component.updateTrait('title', { type: 'select', options: ['Option 1', 'Option 2'], });getTraitIndex(id)—— 返回 Trait 在集合中的索引位置,便于运行时替换:
const traitTitle = component.getTraitIndex('title'); console.log(traitTitle); // 1removeTrait(id)/addTrait(trait, opts)—— 移除与新增 Trait:
component.removeTrait('title'); component.removeTrait(['title', 'id']); component.addTrait('title', { at: 1 }); // at 为插入位置索引 component.addTrait({ type: 'checkbox', name: 'disabled', }); component.addTrait(['title', {...}, ...]);HTML 导出:toHTML / getInnerHTML
toHTML(opts)返回组件完整 HTML 字符串,是导出功能的核心方法。opts支持以下选项:
| 选项 | 类型 | 说明 |
|---|---|---|
tag | String | 自定义 tagName,覆盖组件当前标签 |
attributes | Object \| Function | 传对象时整体替换当前属性;传函数时接收(component, attributes)动态生成属性并返回 |
withProps | Boolean | 将组件属性以data-gjs-*属性形式写入 HTML,得到可重新导入(re-importable)的 HTML |
altQuoteAttr | Boolean | 属性值含"时,用单引号包裹(attr='value "')而非转义(attr="value "") |
基础用法与动态属性示例:
// 简单 HTML 返回 component.set({ tagName: 'span' }); component.setAttributes({ title: 'Hello' }); component.toHTML(); // -> <span title="Hello"></span> // 自定义属性 component.toHTML({ attributes: { 'data-test': 'Hello' } }); // -> <span>// 获取/更新名称 component.getName(); // 返回名称字符串 component.getName({ noCustom: true }); // 忽略自定义名称 component.setName('New name'); // 更新名称 // 图标 component.getIcon(); // 返回图标字符串 // ID component.getId(); // 返回组件 id component.setId('new-id'); // 设置新 id,返回 this // 仅已渲染组件可用 component.getEl(frame); // 获取 DOM 元素(可指定 Frame) component.getView(frame); // 获取对应 ComponentView(可指定 Frame)getEl/getView在源码中均依赖已渲染的视图实例(getEl),多 Frame 场景下可传入Frame指定获取哪个画布中的元素。
遍历与元信息
onAll(clb)—— 对自身及所有内部组件执行回调(含递归),返回this:
component.onAll(component => { // do something with component });实现(源码)先执行自身回调,再对每个子组件递归调用onAll。
forEachChild(clb)—— 仅对全部子组件执行回调(不含自身):
component.forEachChild(child => { console.log(child); });其他元信息方法:
component.props(); // 返回全部属性对象(内部 attributes) component.index(); // 返回组件在父集合中的索引(无父集合时为 0) component.getChangedProps(res); // 仅返回有变化的属性(Partial<ComponentDefinition>)props()直接返回this.attributes,index()通过collection.indexOf(this)计算(见 源码)。
拖拽模式:absolute / translate
setDragMode(value)/getDragMode()用于切换组件的拖拽模式,可选值为'absolute'|'translate'|'':
component.setDragMode('absolute'); // 切换为绝对定位拖拽 component.getDragMode(); // -> 'absolute'底层通过dmode属性存储(见 源码)。absolute模式在拖拽时直接修改 top/left 坐标,translate模式使用 CSS transform 实现更平滑的移动,适合实现画布内的自由布局编辑。
Symbols 覆盖:setSymbolOverride / getSymbolOverride
GrapesJS 的 Symbols(符号)功能(参见 Symbols 指南)允许复用组件并保持一致性:Main Symbol 的修改会自动同步到所有 Instance Symbol。而setSymbolOverride提供了"局部脱离同步"的能力:
component.setSymbolOverride(['children', 'classes']);- 传
true时,该组件后续任何属性变更都不会再传播到相关 Symbols; - 传属性名数组时,仅这些属性的变更会跳过传播(其余仍保持同步);
- 传字符串等价于单元素数组。
getSymbolOverride()返回当前的覆盖值(Boolean或Array<String>)。内部通过 Symbol 专用的隐藏属性(源码中的keySymbolOvrd,见 setSymbolOverride)存储。典型场景是:某个 Instance 需要在保持 Symbol 关联的同时,允许用户独立修改个别属性(如颜色),而不会污染所有实例。
综合实战:组合运用 Component API
下面是一个综合示例,演示如何将上述 API 组合用于常见的"选中组件后动态改造"场景:
// 1. 获取当前选中组件并确认类型 const cmp = editor.getSelected(); if (!cmp || !cmp.isInstanceOf('text')) return; // 2. 调整标签、属性与样式 cmp.set({ tagName: 'span' }); cmp.addAttributes({ 'data-key': 'value', title: 'Hello' }); cmp.setStyle({ color: 'red' }); // 3. 追加子组件并定位到第一个 const added = cmp.append('<strong>Bold text</strong>', { at: 0 }); console.log(added[0].getName()); // 4. 在树中检索与验证 const strong = cmp.find('strong')[0]; console.log(strong.isChildOf(cmp)); // true console.log(cmp.contains(strong)); // true console.log(cmp.findType('text').length); // 渲染前也可用 // 5. 导出验证 console.log(cmp.toHTML({ withProps: true }));结语
Component 是 GrapesJS 数据模型与画布交互之间的枢纽:所有属性变更即时映射到画布与导出代码。本文从属性清单、生命周期钩子、树形检索、属性/样式/类操作、子组件管理、Traits、HTML 导出、拖拽模式到 Symbols 覆盖,完整覆盖了 Component API 文档 的全部内容,并结合 Component.ts 源码 揭示了各方法的底层实现逻辑。掌握这套 API 后,你可以自信地编写自定义组件类型、构建程序化模板编辑逻辑或实现高级的批量操作能力。若需了解组件集合(Components Collection)与类型注册机制,可继续阅读 Components 模块 与 组件集合 API。
【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考