組件:編輯世界的火種(Allspark)

console.info

我是小斑,一個富文本編輯器,今天聊聊咱這編輯器的基本組成:組件!在個人世界裏:Everything is a Component. 大到一篇文章,小到一個字符,都是一個組件。javascript


思考

3 個月前,阿飛也就是個人創造者,寫下第一行代碼前,思考過這樣一個問題:如何描述一篇文章?或者說:是什麼組成了一篇文章?對於這個問題,你們都有本身的答案:文字、段落、圖片、標題、表格、列表等等。但軟件開發須要嚴謹的邏輯,繼續往下思考,就會引出一些問題:html

  1. 文字是段落、標題的組成,而不是文章的組成;
  2. 列表、表格確實是文章的組成,但組成它們的又是什麼?段落?標題?那列表嵌套列表該如何表示?
  3. 關於圖片:表情包之類和文字並排放置的圖片,和佔用一陣行的圖片,明顯不是同一類;
  4. 標題和段落看似兩種類型,但除了基礎樣式不一樣外,它們行爲卻又是相同的;
  5. ···

在軟件開發上,有一準則:前端

任何一個複雜的問題,均可以拆解爲一個個小而簡單的組成。

所以,在寫下小斑的第一行代碼前,阿飛帶着這些問題,經歷整理歸類,類似內容抽象提取後,最終得出如下類型關係圖,並由此得出結論:文章也是一個組件,由內容塊(Block)組成。java

image

雖然說圖片中有如此多的組件,按是否抽象,可分爲兩個陣營,抽象組件與具象組件。git

那就先聊聊抽象組件。github


抽象組件

這麼多的組件中,近一半是抽象組件,因爲具象組件是抽象組件的具象化呈現,充分了解抽象組件後,具象組件的含義就能夠輕鬆理解了。markdown

Component

Component 組件是全部組件的基石,就像變形金剛裏的火種,是全部組件最基本的構成,Component 賦予了組件如下能力:編輯器

  1. 管理組件的樣式信息;
  2. 管理組件內容變動時的歷史棧;

同時,做爲全部組件的基類,Component 還規定了組件必須實現的方法,或是必須明確的屬性:函數

  1. 肯定組件類型,type 屬性;
  2. 實現 render 方法,規定組件該如何渲染本身;

具體實現:Componentui

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 到這兩點的精髓,處理平常問題也會更駕輕就熟哦 ~

好啦,小斑課堂到這結束,但願你們多多使用斑碼編輯器哦!愛大家哦~ image

我是小斑,我爲本身帶鹽!

相關文章
相關標籤/搜索