关于编辑器:组件编辑世界的火种Allspark

3次阅读

共计 3330 个字符,预计需要花费 9 分钟才能阅读完成。

console.info

我是小斑,一个富文本编辑器,明天聊聊咱这编辑器的根本组成:组件!在我的世界里:Everything is a Component. 大到一篇文章,小到一个字符,都是一个组件。

思考

3 个月前,阿飞也就是我的创造者,写下第一行代码前,思考过这样一个问题:如何形容一篇文章?或者说:是什么组成了一篇文章?对于这个问题,大家都有本人的答案:文字、段落、图片、题目、表格、列表等等。但软件开发须要谨严的逻辑,持续往下思考,就会引出一些问题:

  1. 文字是段落、题目的组成,而不是文章的组成;
  2. 列表、表格的确是文章的组成,但组成它们的又是什么?段落?题目?那列表嵌套列表该如何示意?
  3. 对于图片:表情包之类和文字并排搁置的图片,和占用一阵行的图片,显著不是同一类;
  4. 题目和段落看似两种类型,但除了根底款式不同外,它们行为却又是雷同的;
  5. ···

在软件开发上,有一准则:

任何一个简单的问题,都能够拆解为一个个小而简略的组成。

因而,在写下小斑的第一行代码前,阿飞带着这些问题,经验整顿归类,类似内容形象提取后,最终得出以下类型关系图,并由此得出结论:文章也是一个组件,由内容块(Block)组成。

虽说图片中有如此多的组件,按是否形象,可分为两个营垒,形象组件与具象组件。

那就先聊聊形象组件。

形象组件

这么多的组件中,近一半是形象组件,因为具象组件是形象组件的具象化出现,充沛理解形象组件后,具象组件的含意就能够轻松了解了。

Component

Component 组件是所有组件的基石,就像变形金刚里的火种,是所有组件最根本的形成,Component 赋予了组件以下能力:

  1. 治理组件的款式信息;
  2. 治理组件内容变更时的历史栈;

同时,作为所有组件的基类,Component 还规定了组件必须实现的办法,或是必须明确的属性:

  1. 确定组件类型,type 属性;
  2. 实现 render 办法,规定组件该如何渲染本人;

具体实现:Component

Inline

Inline 代表一个行内的块,是光标能够操作的最小局部:字符、表情图片、公式(实现中)都派生于 Inline 类。

Inline 类治理了行内块与内容块(Block)之间的分割。

具体实现:Inline

Block

Block 代表内容块,是组成汇合的最小单位。一篇文章就是一个 Block 的汇合,列表、表格也是,同时列表、表格又是 Block 的派生类,那么列表嵌套列表这种构造就能轻松的示意了。

那内容块(Block)须要实现哪些根底操作呢?

  1. 治理父组件信息;
  2. 实现不同内容块(Block)之间的相互转换;
  3. 与父组件之间的互动:增删改查等行为;
  4. 与兄弟组件的互动,比方:将本人合并到前一组件(在组件最前端触发删除),或接管后一组件;

根据分工的不同,Block 组件又能够派生出 3 类组件:PlainTextCollectionMedia

具体实现:Block

PlainText

PlainText 为纯文本组件,其内容为纯字符,体现与代码编辑器统一,派生出 Code 组件。

具体实现:PlainText

Media

Media 为多媒体组件,具象组件,可生成图片、视频、音频的内容块,实现了 Block 规定的所有办法。

具体实现:Media

Collection

Collection 代表汇合,为一系列组件的容器,管制其子组件的出现成果。

容器组件的次要工作就是对子组件的增删改查。

根据子组件的类型,汇合组件能够拆分为 Inline 的汇合(ContentCollection),与 Block 的汇合(StructureCollection)。

具体实现:Collection

ContentCollection

ContentCollectionInline 组件的汇合,蕴含一连串文字、表情图片、行内公式。

依据应用场景的不同,派生出题目、段落、表格项组件。

具体实现:ContentCollection

StructureCollection

StructureCollectionBlock 组件的汇合,通过 StructureCollectionBlock 的配合、嵌套,就能够残缺的体现出一篇文章、列表等。

依据应用的场景,派生出列表、表格、文章等组件。

具体实现:StructureCollection

具象组件

通过一步步的形象,形象组件派生出的具象组件已不需编写太多的代码去实现相应的性能,但却有一个最重要的办法,必须得本人实现:render

render 函数在 Component 组件下定义,是组件对外出现的路径。

那如何进行 render 呢?简略的生成 Html?小斑的指标可是生成任意环境下的文章,包含但不限于 MarkdownHtml,但 render 办法只有一个,如何生成多变的内容呢?

内容生成器,就该退场啦!

内容生成器

道家哲学有一句话说的好:以不变应万变!

对应到小斑的世界里,不变的是文章的构造,变的是生成的内容,那如何以不变的内容,去生成不同的内容呢?

既然组件对渲染不同的后果无能为力,那何不把渲染这个工作外包给业余的团队呢?

查看以下代码:

class XXX extends Component {render() {return getContentBuilder().buildArticle(
      this.id,
      this.conent, // 代表组件的内容
      this.decorate.getStyle(), // 组件的款式信息
      this.decorate.getData() // 组件所携带的信息);
  }
}

通过 getContentBuilder 获取生成器,通知生成器来个 Article,而后把本人所持有的属性,内容一通扔给生成器,生成什么我不论,大手一挥,躺下喝茶!

ps: 为什么不把组件给扔给生成器呢?大家都晓得 JS 是一门高度动静的语言,只有获取了原对象,就能够对这个对象胡作非为,为了保障组件不被批改,为了保护爱和正义,把生成组件所必须的内容交给生成器就能够啦!天下太平 ~

这里大家可能会问:为什么要通过 getContentBuilder 办法获取生成器,生成器作为参数间接传入不能够吗?

能够当然是能够的,但试想,如果我须要渲染一篇文章,理论调用 render 的是 Article 组件,其余组件通过层层递归的形式调用 render 函数,当然将生成器顺次传入每个组件中能够解决这个问题,然而耦合性太高,不利于开发与保护,因而为了将组件的外部逻辑与渲染行为彻底的封开,把获取生成器这个动作再次外包进来,切实是香的不行!

因而这里其实有两个外包(代理)动作:

  1. 将组件的渲染动作外包给生成器,也就是 buildArticle 的局部;
  2. 将获取生成器的动作外包给一个叫 getContentBuilder 办法;

既然这里用了双重外包,这么简单的概念,益处在哪呢?且听我细细道来:

  1. 将组件的外部逻辑与渲染严格离开,文章的构造归构造,出现归出现,如果渲染有问题,那间接去生成器中找问题即可;
  2. 雷同的代码,却能产生不同的后果,因为 getContentBuilder 是一个办法,它能够返回不同的生成器;
  3. 模式固定,代码简洁,且高度解耦。
  4. 生成器也是一个类,开发能够通过类的继承,重载等形式,批改组件的出现,而无需关注组件的外部逻辑;

几个曾经实现的生成器:

  1. ContentBuilder:用于生成可编辑的 Html 构造,该类为编辑器的外围类,是 Html 可编辑的外围;
  2. HtmlBuilder:用于生成动态 Html 文本,生成的文本不可编辑,纯文本的 Html
  3. MarkdownBuilder:用于生成动态 Markdown 文本;
  4. BaseBuilder:生成器抽象类,任一生成器必须继承并实现该类下所有的办法;

最初

对于组件的局部,大抵就是这样,太细反而会照成了解上的艰难,明天就到这儿啦。组件到底是如何组成,如何渲染,其实并不是最重要,因为阿飞曾经都弄好啦,最重要的是在文中提到对于软件开发的两点,:

  1. 任何一个简单的问题,都能够拆解为一个个小而简略的组成;
  2. 如何以不变应万变最佳?代理永远是最佳的答案!

小斑的弱小也正是因奇妙的应用这两点哦!置信大家如果能彻底 get 到这两点的精华,解决日常问题也会更得心应手哦 ~

好啦,小斑课堂到这完结,心愿大家多多应用斑码编辑器哦!爱你们哦~

我是小斑,我为本人带盐!

正文完
 0