☰
GrapesJS Trait API 完全指南:组件特性的定义、读写与自定义渲染
2026/9/26 0:51:49 网站建设 项目流程

GrapesJS Trait API 完全指南:组件特性的定义、读写与自定义渲染

【免费下载链接】grapesjsFree and Open source Web Builder Framework. Next generation tool for building templates without coding项目地址: https://gitcode.com/GitHub_Trending/gr/grapesjs

GrapesJS 中的Trait(特性)是组件"设置面板"的核心概念,用户通过它在编辑器中调整组件的属性(attribute)与属性(property),例如<input>的placeholder、type、required。本文以 docs/api/trait.md 的TraitAPI 为骨架,结合仓库源码 Trait.ts 与 Traits.md 模块文档,系统讲解 Trait 的完整属性定义、读写流程、运行时更新以及自定义 Trait 类型的实现原理,帮助你在二次开发中灵活掌控组件的"设置"层。

一、Trait 是什么

在 GrapesJS 中,Trait 定义了组件可被用户调整的参数与行为。用户视角下,Trait 通常呈现为组件选中后的"设置(Settings)"面板;开发视角下,Trait 的默认行为是绑定到组件的 DOM 属性(attribute),也可以绑定到组件模型属性(property)并响应其变化。从源码看,Trait是一个继承自Model的类,每个 Trait 关联一个目标组件(target)与编辑器实例(em),并在构造时完成id的兜底赋值与目标组件的绑定:

// packages/core/src/trait_manager/model/Trait.ts constructor(prop: TraitProperties, em: EditorModel) { super(prop); const { target, name } = this.attributes; !this.get('id') && this.set('id', name); // 未显式指定 id 时,用 name 兜底 if (target) { this.setTarget(target); } this.em = em; }

同时setTarget会根据changeProp决定监听change:${name}(属性)还是change:attributes:${name}(DOM 属性)事件,实现组件变化到 Trait 的单向同步(见 Trait.ts)。

二、Trait 属性(Properties)

Trait的核心属性定义如下(类型声明见 types.ts):

属性类型说明
idStringTrait 的唯一标识,如my-trait-id。不指定时默认取name
typeStringTrait 类型,决定渲染方式。可选值:text(默认)、number、select、checkbox、color、button
labelString|false渲染时显示的标签;设为false可隐藏标签列
nameStringTrait 的关键字,作为 attribute 或 property 的键。changeProp开启时作为属性名,否则作为 attribute 名
defaultString组件上未定义值时的默认值
placeholderString默认输入框中的占位提示(若 UI 类型支持)
categoryString|CategoryTrait 分组,用于设置面板中的归类折叠
changePropBoolean为true时 Trait 值作用于组件 property,否则作用于 attributes

除上表外,defaults()中还有unit(number 类型单位)、step(number 类型步长,默认 1)、value、options等默认项(见 Trait.ts)。

字符串简写与类型化定义

在组件定义中,Trait 可以写成字符串,由TraitFactory自动转换为text类型:

'name', // 等价于 { type: 'text', name: 'name' }

从源码 TraitFactory.ts 可以看到build()的分流逻辑:字符串会走buildFromString,其中name为target时有特殊处理——自动转换为select类型并使用配置的optionsTarget选项:

private buildFromString(name: string, em: EditorModel): Trait { const obj: TraitProperties = { name, type: 'text' }; switch (name) { case 'target': obj.type = 'select'; obj.default = false; obj.options = this.config.optionsTarget as any; // 默认 [{ value: false }, { value: '_blank' }] break; } return new Trait(obj, em); }

三、Trait 核心方法 API

以下方法对应 docs/api/trait.md 中的 API 清单,均可直接调用。

基础取值方法

方法返回值说明
getId()String获取 Trait 的 id(未指定时为 name)
getType()String获取 Trait 类型
getName()String获取 Trait 名称(作为 attribute/property 的键)
getDefault()any获取默认值
getOptions()Array<TraitOption>获取选项数组(select 类型使用)
props()Object返回 Trait 的全部属性对象(见 Trait.ts)

标签相关方法

getLabel(opts)— 获取 Trait 标签。opts.locale默认为true,此时优先使用 i18n 模块中的traitManager.traits.labels.${id}翻译,否则回退到label或name:

getLabel(opts: { locale?: boolean } = {}) { const { locale = true } = opts; const id = this.getId(); const name = this.get('label') || this.getName(); return (locale && this.em?.t(`traitManager.traits.labels.${id}`)) || name; }

getCategoryLabel(opts)— 获取分类标签,locale 开启时查找traitManager.categories.${catId}(见 Trait.ts)。

值读写方法

getValue(opts)— 获取 Trait 值。默认从组件 attributes 取值,changeProp开启时从组件 property 取值;opts.useType为true时按类型归一化(如 checkbox 始终返回布尔值):

getValue(opts?: TraitGetValueOptions) { return this.getTargetValue(opts); }

getTargetValue的内部逻辑(Trait.ts)优先级为:自定义getValue回调 >changeProp时的component.get(name)>component.getAttributes()[name];若useType且类型为checkbox,则对照valueTrue/valueFalse将值归一化为布尔。

setValue(value, opts)— 更新 Trait 值。默认作用于组件 attributes,changeProp开启时作用于 property;opts.partial为true时更新不会被 UndoManager 完整记录:

setValue(value: any, opts: TraitSetValueOptions = {}) { const { partial } = opts; const valueOpts: { avoidStore?: boolean } = {}; if (partial) { valueOpts.avoidStore = true; } this.setTargetValue(value, valueOpts); }

若 Trait 定义了自定义setValue回调(与getValue成对使用),setValue会优先调用该回调,并把value、component、editor、trait、partial、options以及emitUpdate一并传入(见 Trait.ts)。setTargetValue还会把字符串'false'/'true'转换为布尔,并对 checkbox 类型套用valueTrue/valueFalse映射(Trait.ts)。

选项相关方法

方法说明
getOption(id?)获取当前选中项或按 id 查找的选项;不传 id 时用当前值匹配,返回Object \| null
getOptionId(option)从选项对象中取 id,option.id不存在时回退到option.value
getOptionLabel(id, opts)获取选项标签,locale 开启时查找traitManager.traits.options.${name}.${optId},否则用option.label/option.name/optId(见 Trait.ts)

命令执行

runCommand()— 执行按钮类型(button)Trait 绑定的命令。command可以是命令 ID 字符串(走em.Commands.run(command))或函数(以(em.Editor, trait)调用),见 Trait.ts。

四、组件上定义 Trait:从属性绑定到属性绑定

默认情况下 Trait 修改组件的 attributes,因此初始值也要通过attributes声明:

editor.Components.addType('input', { isComponent: (el) => el.tagName === 'INPUT', model: { defaults: { traits: [ 'name', // 字符串自动转为 text 类型 'placeholder', { type: 'select', name: 'type', // (必填) 作用到组件的 attribute/property 名 label: 'Type', options: [ { id: 'text', label: 'Text' }, { id: 'email', label: 'Email' }, { id: 'password', label: 'Password' }, { id: 'number', label: 'Number' }, ], }, { type: 'checkbox', name: 'required' }, ], attributes: { type: 'text', required: true }, // 初始值 }, }, });

开启changeProp: 1后,Trait 值作用于组件 property,初始值也需改从 property 声明,且监听事件从change:attributes:*变为change:*:

editor.Components.addType('input', { model: { defaults: { traits: [{ name: 'placeholder', changeProp: 1 }], placeholder: 'Initial placeholder', // 从 property 设置初始值 }, init() { this.on('change:placeholder', this.handlePlhChange); }, }, });

动态 Trait

Trait 还可以定义为函数,在组件初始化时按需生成,例如根据draggable属性决定返回哪些 Trait(详见 Traits.md):

editor.Components.addType('input', { model: { defaults: { traits(component) { const result = []; if (component.get('draggable')) { result.push('name'); } else { result.push({ type: 'select', name: 'type', options: [...] }); } return result; }, }, }, });

内置 Trait 类型速查

类型关键参数说明
textlabel(可设false隐藏标签列)、placeholder默认类型,简单文本输入
numbermin、max、step、unit数字输入。源码中由 TraitNumberView.ts 借助InputNumber渲染,getValueForTarget会把value + unit拼回组件
checkboxvalueTrue(默认true)、valueFalse(默认false)复选输入。渲染与取值见 TraitCheckboxView.ts,onChange直接以checked布尔写回 model
selectoptions: [{ id, label }]下拉选择。渲染逻辑见 TraitSelectView.ts:字符串选项直接用自身作 name/value,对象选项支持name/label/value/style;选中值不在列表中时回退default
color—颜色选择器
buttontext/labelButton、full(全宽)、command(命令 ID 或函数)按钮,点击执行runCommand()

五、运行时更新 Trait

Trait 是组件的普通属性,运行时可通过 Component API 读取与修改:

const component = editor.getSelected(); // 画布中选中的组件 // 获取全部 traits const traits = component.get('traits'); traits.forEach((trait) => console.log(trait.props())); // 按 name/id 查找单个 trait console.log(component.getTrait('type').props()); // 更新 trait 属性(如 select 的 options) component.getTrait('type').set('options', [ { id: 'opt1', label: 'New option 1' }, { id: 'opt2', label: 'New option 2' }, ]); // 或一次性设置多个属性 component.getTrait('type').set({ label: 'My type', options: [...] });

新增与移除 Trait 使用addTrait/removeTrait,其底层实现在 Component.ts:

// 新增:at 指定插入位置,缺省追加到末尾 component.addTrait({ name: 'type', ... }, { at: 0 }); // 也支持字符串或数组批量添加 component.addTrait(['title', { type: 'checkbox', name: 'disabled' }]); // 移除:支持单个或数组 component.removeTrait('type'); component.removeTrait(['title', 'id']);

addTrait内部调用this.traits.add(...)(集合逻辑见 Traits.ts,字符串经TraitFactory.build转换),两者都会触发ComponentsEvents.toggled事件刷新设置面板。

分类(Categories)

Trait 可按category分组,组内折叠状态可配置(无分类的 Trait 渲染在底部):

const category1 = { id: 'first', label: 'First category' }; const category2 = { id: 'second', label: 'Second category', open: false }; editor.Components.addType('input', { model: { defaults: { traits: [ { name: 'trait-1', category: category1 }, { name: 'trait-2', category: category1 }, { name: 'trait-3', category: category2 }, { name: 'trait-4', category: category2 }, { name: 'trait-5' }, // 无分类,渲染在底部 { name: 'trait-6' }, ], }, }, });

模块层可用editor.Traits.getTraitsByCategory()获取按分类分组后的 Trait 列表(见 index.ts)。

六、Trait 值的底层读写链路

理解值读写链路有助于排查"Trait 为什么没生效"类问题。从源码看,整条链路为:

  1. 输入触发:用户操作输入框,TraitView监听捕获事件(默认change,见 TraitView.ts),onChange先把值写入 model 的value,再调用自定义onEvent。
  2. 写回组件:onValueChange调用model.setValue(value, opts)→setTargetValue→ 根据changeProp分别走component.set(props)或component.addAttributes(props);partial时通过props.__p = null标记避免被 UndoManager 记录。
  3. 组件反向同步:组件属性变化触发setTarget中绑定的change:attributes:${name}(或change:${name})事件 →targetUpdated()→ 以useType: true重新取值并写回 Trait model,同时触发trait:value与trait:update事件(见 Trait.ts)。

对 checkbox 类型的值归一化,getTargetValue的useType分支会对照valueTrue/valueFalse返回布尔;而setTargetValue写回时会反向把布尔映射回valueTrue/valueFalse,二者成对保证了双向一致性。

七、自定义 Trait 类型与自定义 Trait Manager

默认类型覆盖多数场景,需要更复杂 UI 时可用editor.Traits.addType注册新类型,其核心只需三个钩子方法(基于TraitView的extend,见 index.ts):

  • createInput({ trait, component })— 返回 HTML 字符串或 DOM 元素,定义输入控件;
  • onEvent({ elInput, component, event })— 输入变化时更新组件;
  • onUpdate({ elInput, component })— 组件变化时回填输入。

以一个href-next自定义类型为例,先替换link组件的 Trait:

editor.Components.addType('link', { model: { defaults: { traits: [{ type: 'href-next', name: 'href', label: 'New href' }], }, }, });

再注册类型并定义控件与双向绑定:

editor.Traits.addType('href-next', { createInput({ trait }) { const traitOpts = trait.get('options') || []; const options = traitOpts.length ? traitOpts : [ { id: 'url', label: 'URL' }, { id: 'email', label: 'Email' }, ]; const el = document.createElement('div'); el.innerHTML = ` <select class="href-next__type"> ${options.map((opt) => `<option value="${opt.id}">${opt.label}</option>`).join('')} </select> <div class="href-next__url-inputs"><input class="href-next__url" placeholder="Insert URL"/></div> <div class="href-next__email-inputs"> <input class="href-next__email" placeholder="Insert email"/> <input class="href-next__email-subject" placeholder="Insert subject"/> </div> `; // ... 类型切换逻辑(切换 url/email 输入区显示) return el; }, onEvent({ elInput, component }) { const inputType = elInput.querySelector('.href-next__type'); let href = ''; switch (inputType.value) { case 'url': href = elInput.querySelector('.href-next__url').value; break; case 'email': { const valEmail = elInput.querySelector('.href-next__email').value; const valSubj = elInput.querySelector('.href-next__email-subject').value; href = `mailto:${valEmail}${valSubj ? `?subject=${valSubj}` : ''}`; break; } } component.addAttributes({ href }); }, onUpdate({ elInput, component }) { const href = component.getAttributes().href || ''; // ... 按 href 前缀回填 url / email 输入框并同步 select 状态 }, });

补充说明(源码佐证):

  • 事件捕获:默认监听change事件(TraitView.ts 的TraitView.prototype.eventCapture = ['change']);如需在输入过程中实时更新,用eventCapture: ['input']覆盖。
  • 布局定制:createLabel({ label })自定义标签列;noLabel: true强制隐藏标签列(与label: false等效);templateInput可替换默认输入包裹层——传''完全去掉包裹,或传含data-input占位属性的模板(也可为函数)。默认模板见 TraitView.ts。
  • 手动触发:在外部 UI 中要触发onEvent时,使用内置的onChange方法而非直接调用onEvent(如 Vue Slider 集成示例中的sliderInst.$on('change', (ev) => this.onChange(ev)))。

若需要完全替换默认设置面板 UI,初始化时开启traitManager.custom: true并订阅trait:custom事件(配置项定义见 config.ts):

const editor = grapesjs.init({ traitManager: { custom: true }, }); editor.on('trait:custom', (props) => { // props.container (HTMLElement) — 默认容器,可挂载自定义 UI // 在此实现渲染/更新逻辑 });

八、I18n 与事件

Trait 支持通过 I18n 模块国际化,完整 schema 如下(对应 Traits.md 的 i18n 章节):

{ en: { traitManager: { empty: 'Select an element before using Trait Manager', label: 'Component settings', categories: { categoryId: 'Category label', // 分类标签 }, traits: { labels: { href: 'Href label', // 以 trait 的 name 为键 }, attributes: { href: { placeholder: 'eg. https://google.com' }, // 输入 DOM 属性 }, options: { target: { _blank: 'New window', // 以 option 的 id 为键 }, }, }, }, }, }

模块事件(枚举定义见 types.ts):

事件触发时机回调数据
trait:select切换组件导致 Trait 重新选中{ traits, component }
trait:valueTrait 值更新{ trait, component, value }
trait:update任意 Trait 属性变化{ trait, component, value }
trait:category:update分类更新{ category, changes }
trait:custom自定义 UI 需要刷新{ container }
trait以上所有事件的聚合事件{ event, trait, component, value, ... }
editor.on('trait:value', ({ trait, component, value }) => { console.log(`${trait.getName()} -> ${value}`); });

九、总结

本文完整覆盖了 docs/api/trait.md 中Trait的全部属性与方法,并向下延伸了组件定义、运行时更新、分类、内置类型参数、自定义类型钩子与 i18n/事件体系。核心要点回顾:

  1. Trait 本质是绑定到组件 attributes 或 properties 的模型对象,changeProp决定绑定目标;
  2. getValue/setValue构成双向读写入口,partial控制是否进入 UndoManager,useType控制类型归一化;
  3. 字符串简写经TraitFactory转换为text类型,target有内置的 select 特化;
  4. 自定义 Trait 只需实现createInput/onEvent/onUpdate三件套,配合eventCapture、templateInput、noLabel即可完全掌控渲染与交互。

进一步可阅读 Traits 模块文档 了解完整实战示例,或查看 TraitManager 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),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询