這應該是最大的react組件了吧! ?

blank

這應該是最大的react組件了吧! ?

同學,web項目要組件化

jquery那個年代過來的人,組件化帶來的感受是非常讚的,大家都知道,react項目是由很多小的組件組合構成的,小組件又組合成大組件,大組件組合成項目級別的組件,那組件也有大小,有生命週期,那麼下面的<App />也是一個組件。

render(<App/>,document.getElementById('App'))

這裡我們所指的組件是:可複用的組件,是通過npm install xxx安裝使用的組件。

不小心就做了一個超級大組件

這次分享的項目是h5ds ,一個超級大的react組件,可以通過npm install h5ds安裝使用這個組件。目前來看,這應該是目前最大的React組件了吧。

1. 什麼是H5DS?

H5DS (HTML5 design software)可以理解成一款做H5的在線工具,H5就是在手機上滑動的頁面,像易企秀,百度H5,wps秀堂,Maka一樣的在線工具。

最初我只是想做一艘獨木舟,做著做著,感覺加個帆要好點,然後就一發不可收拾,不停的添加新的功能,獨木舟變成了大船,最終變成了一艘戰艦。

任何項目只要一進入迭代周期。哪怕是一個很小的項目,最終也可能成長成為一個龐大的項目,所以不要低估自己處理問題的能力,只要放手去幹,總會有驚喜。

工具截圖範例:

blank

製作的H5範例:

blank

接下來會講解我開發這個項目的全部過程和技術方案。

2. 技術選型

技術選型原則:盡量以節約開發量為主,不重複造輪子,我們是為了生產一個成品,不是原料加工廠,有現成的組件就用上,有節約開發工作量的框架也用上。

【前端:】

React :模塊化開發少不了,angular,react,vue三選一,這裡選擇了react。

Jquery :雖然很多同學說這個已經淘汰了,不過對於小團隊開發而言,開發資源是非常寶貴的,能一句代碼解決的問題用兩句代碼來解決就是浪費研發資源。當然使用這個還有另外一個更重要的原因,為了支持海量的jquery插件,我們犧牲了一部分性能。

mobx :從技術角度,經過我們分析的最佳方案並不是mobx,而是redux,redux更適合這樣的工具項目,但是考慮到代碼量和二次開發的成本,我最終選擇了mobx,對於使用過vuex的用戶而言,mobx也方便掌握,也很容易從vue轉到react項目中來。

less :沒有選擇sass的原因主要是因為node-sass包不是很穩定,經常出錯,當然less已經能滿足我們的需求了。

antd :我們不造輪子,這樣的工具項目對組件的需求是非常大的,有現成的優秀的react組件庫當然要用起來。沒有的自己再封裝一些就可以了。

loadsh :工具類項目對數據的處理是非常多的,這裡用到了loadsh裡面的一些方法去處理數據。

【後端:】

koa :後端語言採用nodejs,koa文檔和學習資料也比較多,express原班人馬打造,這個正合適。

mysql :免費的關係型數據庫,這個算是比較常規的了。

Sequelize :Sequelize是一款基於Nodejs功能強大的異步ORM框架,既然有現成的,我們也不自己去封裝了。工作量能節約就節約。重心放到業務上。

3. 系統架構方案

需求用一句話概括:編輯器生成H5頁面,可以在手機端打開。

那麼編輯器和H5頁面應該是分開的兩個項目,H5裡面有很多模塊模塊(plugins插件),比如圖片,文本,形狀,視頻,音樂等,後續可能還會新增其他插件,所以這塊業務就必須是可擴展的。因為我們採用mobx管理數據,數據我們採用json數據,這裡我們至少有三種方案:

  1. 編輯器生成JSON數據,服務端根據JSON數據生成HTML代碼提供H5預覽頁面。

blank

優點:服務端直接返回HTML頁面,可以做SEO
缺點:插件需要在服務端加載使用,脫離服務端將沒法跑起來,如果訪問量大,因為服務器壓力比較大。

  1. 編輯器直接生成HTML代碼,服務端將HTML代碼另存為HTML文件,返回URL提供訪問。

blank

優點:服務端直接返還HTML頁面,可以做SEO,服務端訪問壓力小,可脫離服務端獨立運行缺點:數據不夠靈活,只能通過編輯器修改後重新發布才可以,後續升級預覽頁面的代碼沒法做到同步升級。

  1. 編輯器直接生成JSON數據,服務端只負責存取JSON數據,渲染交給前端處理。

blank

優點:服務端壓力小,可脫離服務端獨立運行,數據靈活,能實現模版同步升級缺點:不支持SEO

綜合評估,我們選擇了第三種方案,預覽頁面直接依賴插件,優點明顯,後續可升級的空間大。如果要做SEO,也可以做SSR,但是目前對SEO的需求不是很大,因為主要是微信H5為主。

4. 數據結構

架構方案確定後,數據結構也是非常重要的,提供H5頁面需求來看,數據結構大致是這樣的:

{...infos,// 记录H5的訊息,名称,主图,描述
...options,// 记录H5的配置訊息,滑动效果,类型等
pages:[// 记录页面数据
{...infos,// 页面訊息
...options,// 页面配置
layers:[...]// 记录页面的图层訊息
}]}

整體看上去數據結構就非常清晰了,圖層和頁面的概念也是H5的核心。

一個頁面由多個圖層構成,而圖層又有多個種類(圖片,文本,形狀,視頻,音樂),也就是之前說的可擴展插件(plugins插件)

實際上數據結構是非常複雜的,我們的數據如下:

h5ds json數據結構v1.0版本

appJSON數據結構:

{version:5.0.0,//當前的H5DS版本img:'http://cdn.h5ds.cn/static/images/img-null.png',//主圖desc:'點石H5,官方網站h5ds.cn',//描述訊息name:'點石H5',//應用名稱type:'phone',// h5類型phone or pcslider:{//全局的翻頁設置speed:0.5,//切換速度effect:'slide',//翻頁動畫slide, fade, coverflowautoplay:false,//是否自動翻頁time:5//自動翻頁時間},style:{// app的樣式width,height},fixeds:[//浮動層有兩個,不可刪除和添加。上層浮動和下層浮動{id:null,// div.idclassName:null,// div.classkeyid:util.randomID(),// keyid是一個不重複的隨機數,相當於是idname:'浮動層上',//名稱desc:'頁面描述',style:{//浮動層的樣式height,width},layers:[layerJSON]//浮動層中的layer集合},{id:null,className:null,keyid:util.randomID(),name:'浮動層下',desc:'頁面描述',style:{height,width},layers:[]}],popups:[pageJSON],//彈窗數據集合,pages:[//頁面數據集合{id:null,className:null,keyid:util.randomID(),name:'頁面名稱',desc:'頁面描述',style:{height,width},//頁面樣式,background-color樣式會單獨在外層div渲染layers:[layerJSON],//當前頁面的圖層slider:{//當前頁面的翻頁設置autoplay:false,lock:false,time:5}}]}

pageJSON 數據說明:

{ id : null , // 页面id className : null , // 页面class keyid : util . randomID (), // 唯一标识name : '页面名称' , desc : '页面描述' , style : { height , width }, // 页面样式,background-color样式会单独在外层div渲染layers : [ layerJSON ], // 当前页面的图层slider : { // 当前页面的翻页设置autoplay : false , lock : false , time : 5 } }

layerJSON 數據說明:

//每個layerJSON數據是不一樣的,他們都遵循一定的規則,data參數是不一樣的{version:'1.0.0',//插件版本號name:'地圖插件',//插件名稱pid:'h5ds_map',//插件的idid:null,//圖層的idclassName:null,//圖層的classset:{hide,lock,lockWideHigh,noEvent},//鎖定,隱藏, lockWideHigh鎖定寬高比等設置, noEvent表示可以事件穿透animate:[animateJSON],//圖層的動畫,可以支持多個動畫data:{...},//組件差異化相關的數據存放位置style:{width:100,height:100,top:0,left:0},//圖層組件的外層默認樣式estyle:{},// element div的樣式,style樣式只有4個參數,其他的樣式均寫到estyle中,比如背景,因為動畫參數設置在element div上,所以這裡不能設置transform樣式events:[eventJSON]//事件配置數據,每個圖層可以添加多個事件}

5. 技術難點

  1. 數據管理:

我們的數據丟到mobx進行管理,數據變化,直接更新視圖,這個很vue的數據管理有點相似。裡面會涉及到很多數據問題。這時候就需要我們去定義一些全局的方法。我定義了一個h5ds的store,在store裡面保存了兩個數據(edata, data),其中edata是editor data的簡寫,用於記錄用戶在操作編輯器的交互數據。比如當前選中了哪個頁面,哪個圖層,以及一些全局的配置參數。 data則用於保存H5的json數據。為了保證瀏覽器突然崩潰,導致用戶數據被清除,我做了歷史操作數據,會把編輯的數據保存到localstorage。

  1. 撤銷回退

操作記錄是保存了當前的edata和data數據到內存,為了節約內存,只保存了20次最新的操作,隨時可以通過撤銷回退到上一步操作。如果使用了redux,天然數據回滾,非常方便。

  1. 數據更新

在編輯器內部數據更新是非常頻繁的,因為數據嵌套太深,mobx的proxy監聽數據是不會去監聽對像或者數組內部的數據,所以需要手動觸發視圖更新,這裡寫了一個全局的方法updateCanvas()去強制更新整個視圖,為了做性能方面的考慮,在拖動位置或者修改大小的時候,只去修改某個圖層的視圖。每個layer有一個key,通過修改這個key可以實現單個組件的更新。

  1. 圖層插件的打包

圖層插件在編輯器中和預覽頁面都會用到,這裡就會涉及到復用了,我們把插件分為Layer.js 和Editor.js 。其中Layer.js 是在編輯器和H5預覽頁面都會用到,Editor.js 只在編輯器中用到。為了減少預覽頁面的代碼,我們單獨打包了2份UMD包,在不同的地方去使用,最初我們採用requirejs去管理UMD包,但是後來發展requirejs有各種問題,可能和一些第三方的包衝突,所以我們把插件直接掛載到window對像下面,使用H5DS_GLOBAL對象存儲起來,雖然很暴力,但是這種方法真的非常實用。

  1. 拖動縮放旋轉

針對拖動縮放旋轉,這塊很有意思,如果用react的方式,會在每個選中的元素外麵包裹一層拖動的組件,如果是用判斷去動態加載這個拖動組件,圖層一定會被更新的,所以我們用了一種很巧妙的方法,用jquery封裝了一個托拉拽的插件,在componentDidMount裡面去綁定事件,初始化這個插件,這種方法雖然是不被推薦的,可以用奇巧淫技來形容吧。但的確大大減少了我們的維護成本。用起來也非常方便。

6. 性能優化

我們可以結合防抖函數去做性能優化,控製或者選擇性的去更新視圖。下面舉個例子:

importReact,{Component}from'react';import{inject,observer}from'mobx-react';importdebouncefrom'lodash/debounce';@inject('h5ds')@observerclassDemoextendsComponent{constructor(props){super(props);this.state={count:props.h5ds.count//默認是1}}//防抖函數控制性能updateOtherRender=debounce(()=>{const{count}=this.state;//如果大於10才會去更新其他地方的視圖if(count>10){this.props.h5ds.count=this.state.count}},500)changeValue=e=>{this.setState({count:e.target.value},this.updateOtherRender)}render(){return<inputtype="number"value={this.state.count}onChange={this.changeValue}/>}}

我們在很多地方都有用到上面這種寫法,react提倡的最小模塊化,我們也希望模塊之間的影響會最小,如果一個參數在多個模塊中被使用,在快速輸入的時候務必會變的很慢。

7. 全終端適配方案

手機的分辨率太多了,要兼容全部機型,一種兼容方案是遠遠不夠的,這裡我提供了多種兼容方案。

第一種: 等比縮放

blank

我們的默認寬度高度是320px * 514px,然後進行縮放,自動撐滿高或者寬度,如圖所示的陰影部分,當然上下或者左右可能預留一部分邊框沒有任何顯示。

第二種:全屏背景

因為會存在上下或者左右有間隙的情況,這時候我們把背景顏色做全屏處理,比如紅色是背景色,如果高度超過了,我們的H5頁面會自動出現滾動條。

第三種:吸附定位

吸附定位這個名詞是我自己取的,有時候要兼容到iphoneX是很麻煩的,吸附定位表示可以吸附到頂部或者其他相對位置,我們的吸附定位提供了8個位置可以吸附。吸附後,會以相對window的位置進行定位,而不是相對陰影的定位。比如針對小紅心:

開啟吸附前:

blank

開啟左上角吸附後:

blank

8. 如何使用H5DS

  1. npm install h5ds --save安裝依賴包。
  2. webpack配置:
externals:['React','ReactDOM','ReactRouter','ReactRouterDOM','mobx','_','antd','PubSub','moment']
  1. 使用h5ds

html模版:

<!DOCTYPE html><html><headlang="zh-cn"><metacharset="utf-8"><metahttp-equiv="X-UA-Compatible"content="IE=edge"><metaname="description"content=""><metaname="keywords"content=""><metaname="viewport"content="width=device-width,initial-scale=1"><title>H5DS5.0</title><metaname="renderer"content="webkit"><!-- No Baidu Siteapp--><metahttp-equiv="Cache-Control"content="no-siteapp"/><metaname="apple-mobile-web-app-title"content="yes"/><metaname="apple-mobile-web-app-capable"content="yes"><metaname="apple-mobile-web-app-status-bar-style"content="black"><metahttp-equiv="Cache-Control"content="no-siteapp"/><linkrel="shortcut icon"href="/assets/images/favicon.ico"><linkrel="stylesheet"href="https://at.alicdn.com/t/font_1160472_ybl2xl0ao8.css"><linkrel="stylesheet"href="https://at.alicdn.com/t/font_157397_ujac0trx9i.css"><linkhref="https://cdn.bootcss.com/antd/3.23.0-beta.0/antd.min.css"rel="stylesheet"><linkhref="https://cdn.h5ds.com/lib/plugins/swiper.min.css"rel="stylesheet"><scriptsrc="https://cdn.h5ds.com/lib/plugins/swiper.min.js"></script><scriptsrc="https://cdn.h5ds.com/lib/plugins/jquery.min.js"></script><scriptsrc="https://cdn.h5ds.com/lib/plugins/h5ds.vendor.min.js"></script><scriptsrc="https://cdn.bootcss.com/moment.js/2.24.0/moment.min.js"></script><scriptsrc="https://cdn.bootcss.com/antd/3.23.0-beta.0/antd.min.js"></script></head><body><divid="App"></div></body></html>

react代碼:

import'h5ds/editor/style.css';import{render}from'react-dom';importReact,{Component}from'react';importH5dsEditorfrom'h5ds/editor';classEditorextendsComponent{constructor(props){super(props);this.state={data:null};}/***保存APP*/saveApp=asyncdata=>{console.log('saveApp ->',data);};/***發布app*/publishApp=asyncdata=>{console.log('publishApp ->',data);};componentDidMount(){//模擬異步加載數,設置defaultData會默認加載一個初始化數據setTimeout(()=>{this.setState({data:'defaultData'});},100);}/***使用編輯器部分*/render(){const{data}=this.state;return(<H5dsEditorplugins={[]}//第三方插件包data={data}options={{publishApp:this.publishApp,saveApp:this.saveApp,//保存應用appId:'test_app_id'//當前appId}}/>);}}// renderrender(<Editor/>,document.getElementById('App'));

結束

最後感謝各位的閱讀!

我們的官方網站是: http://www.h5ds.com

我們的git地址是 github.com/h5ds/h5ds

工具編輯器發布目前已經比較成熟,還在迭代中,我們希望能有更多的開發者能參與進來,開發插件,讓H5DS編輯器能給各種領域帶來便利。

技術交流QQ群:549856478

如果你覺得有錯誤的地方,或者有任何好的建議,歡迎issue我們!

What do you think?

Written by marketer

blank

騰訊視頻Node.js 服務是如何支撐國慶閱兵直播高並發的?

blank

螞蟻中後台快速研發平台的領域思考