React Editor 應用編輯器(1)

ascoders發表於2016-09-26

這是視覺化編輯器 Gaea-Editor 的第一篇連載分析文章,希望我能在有限的篇幅講清楚製作這個網頁編輯器的動機,以及可能帶來的美好使用前景(畫大餅)。它會具有如下幾個特徵:

  1. 執行在網頁
  2. 文件流佈局,絕對定位同時支援
  3. 對插入的任何 React 元件都可以直接作為編輯元素拖拽到頁面中
  4. 相容 React-Native 的 web 元件可以讓它生成 android 和 ios 原生頁面
  5. 擁有 Gaea-Preview 套件,傳入 Gaea-Editor 生成的 json,可以立刻生成頁面
  6. 擁有 Gaea-web-components Gaea-native-components 分別提供網頁、原生基礎最小粒度的元件
  7. 可以定製任何 React 元件插入到編輯器中
  8. 像 chrome-devtools 一樣靈活,可以跨層級排序拖拽任何編輯區的元素
  9. 可以自定義組合模板,三下五除二搞定相似的需求

當然看完這篇文章,不僅限於瞭解這個編輯器的功能,我會非常詳細介紹其設計細節,只要你仔細讀它,完全可以做出自己的網頁編輯器 ^_^。

在說這個視覺化編輯器之前,不得不提到 React,這是我創作它的動機。雖然不確定 React 能火多久,但它帶來的元件化掀起了一場前端界的工業革命,當然,元件化這個理念也不是 React 首創,但 React 大大降低了元件化的成本,就像發明了活字印刷術,讓只有貴族才買得起的書本普及到了千家萬戶。

在全民元件化的時代裡,我寫過幾篇文章介紹如何應用和管理元件 以及元件庫的維護經驗。現在元件化正在越來越普及,我們掌握了元件開發和管理的規律後,專案結構組織,團隊間協作已經取得了飛速進步,元件化帶來效率的提升也會日漸枯竭,但視覺化編輯可能是一條突破瓶頸之路,第一,在有了現成元件的基礎上,將其遷移到視覺化編輯平臺的成本非常小,第二,程式碼之外的頁面開發更加直觀,加上部分程式碼的輔佐會讓結構組織更高效(類似 Unity 引擎)。

React 與原生拖拽結合

網頁編輯器第一步,也是最重要的一步,就是拖拽功能了,我們希望最終效果如圖所示:

React Editor 應用編輯器(1)

如圖所示,支援隨意拖拽、拖拽動畫,跨父級拖拽。我們使用 sortablejs 可以達到此效果,這篇文章重點就是介紹如何結合到 React。

使用 sortable.js

為了支援巢狀拖拽,我們使用開發版地址安裝 "sortablejs":"git://github.com/RubaXa/Sortable.git#dev"

將 sortable 與 react 結合我們首先會想到在拖拽結束後重新 render,但這樣做有如下幾個缺點:

  • sortable 因為拖拽過程中改變了 dom 結構,所以操作流暢,但因此生成的 dom 節點脫離了 react 的控制
  • 排序拖拽後會,sortable 會刪除之前拖拽的節點,導致 react diff 演算法刪除元素時發現 dom 已經消失

總結來說就是既要讓 sortable 操作 dom,又不能讓 dom 操作導致脫離 react 的控制,我們採用操作回放的方式,將 sortable 操作結束後的 dom 修改回退,再將操作結果狀態用 react 重新整理

右側選單配置

對右側選單配置如下:

如上程式碼註釋寫的很詳盡,解釋一下就是,從選單拖拽的配置要用 pull:clone 的方式配置,這樣同一個元素才可以拖拽多次。put:false 讓選單不能被其它元素拖入。

當開始拖拽時,儲存拖拽後的位置,便於找到使用者拖拽的元素,在頁面生成例項,同時儲存拖拽前的位置,便於拖拽結束後恢復元素。

所以拖拽結束後,先判斷 event.clone.parentNode,如果是空,說明元素並沒有被拖走,所以不需要處理,否則需要先刪除原先位置留下的 clone dom,因為這個元素不受 react 控制,再將真實拖走的元素還原到之前的位置

檢視區域配置

編輯器檢視區域的 sortable 配置比較長,因此拆解分析。

group 配置:

這個很容易理解,因為檢視區域的元素可以被移走,也可以被其它元素移入,因此 pullput 都是 true

開始拖拽時

拖拽結束時

拖拽結束不需要做特殊處理,但可以做一些視覺設定,比如告訴使用者拖拽結束了。

有元素新增時

有元素新增後,有兩種情況:新增元素,或者從已有元素中拖拽進來新增。

如果是從工具欄拖拽進來新增的元素,只需要用 react 重新渲染一遍即可。

如果是從其它檢視元素中移入進來的,需要把這個元素還原到之前拖拽的位置,這樣就回退到 sortable 操作之前的狀態,再用 react 渲染這兩個父級元件。

同一父級內元素位置更新時

我們只需要對元素位置進行還原,之後根據起點位置和終點位置模擬元素移動,再使用 react 渲染即可。這裡需要注意, sortable 的拖拽不是簡單的a b互換,而是 a -> b,下面用圖簡單描述一下:

React Editor 應用編輯器(1)

 

如上圖所示,同一個父級下有6個元素,當我們拖拽第一個元素到第5個元素時,排序不是變成了 5 2 3 4 1 6,而是如下圖所示:

React Editor 應用編輯器(1)

不可避免的產生了互換,我們逐一互換元素位置,然後更新父級元素子元素的位置。注意此時最佳狀態是不觸發 react 元素渲染,我們只要保證子元素的 key 不變, react diff 演算法會自動移動 dom 節點,而不是重新渲染 1 2 3 4 5 這 5 個子節點。

當元素被移走時

當元素被移走時,不會觸發 onUpdate 方法,而會觸發 onAdd 方法,但是我們已經在 onAdd 方法中將移走的元素還原回去,因此這裡不需要做任何處理,相當於沒有改動,我們只需要更新 react 父級元素重新渲染,讓 react 將元素移走即可。

總結

基於以上選單區域和檢視區域的博弈,終於將 sortable 與 react 渲染完美結合起來,然而不用擔心有什麼副作用,因為我們已經將所有 sortable 的操作還原,所以實際上只用了它的拖拽過程已經拖拽結果,忙到後來其實沒有改變任何 dom 結構,最終 dom 元素的變化還是由 react 來控制。

後續系列我們會繼續剖析實現部分,以及放上倉庫地址。解析到底是如何將元素放在檢視區域,並且並支援無限層級巢狀的,敬請期待!

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

任選一種支付方式

React Editor 應用編輯器(1) React Editor 應用編輯器(1)

相關文章