目前較為流行的react確實有很多優點,例如虛擬dom,單向數據流狀態機的思想。還有可復用組件化的思想等等。加上搭配jsx語法和es6,適應之后開發確實快捷很多,值得大家去一試。其實組件化的思想一直在提,原來的開發中也會抽一些公共的模塊出來。但是react帶來的思想沖擊是革命性的,套用一句可能不太合適的話來,描述:萬事萬物皆組件,在這種思想的影響下,不管什么框架都可以抽一些公共的模塊出來,應該秉持一種心態:任何代碼都盡量不要重復寫兩遍,如果存在那么就可以考慮封裝起來作為組件。當然不是一味的提倡盲目抽離,這個度還是要把握好的。
但是,人無無人,何況物乎。react雖好,但是目前國內的現狀是還存在一個讓人頗為頭疼的瀏覽器系列——微軟的ie系列。雖然微軟最新推出的ie系列已經不再那么特立獨行了,但是用戶不一定也會升級呀。我一個瀏覽器能用就行,干嘛費那個勁隨時升級,而且有這個想法的不在少數。所以面向用戶的系統,根據我們自己統計的瀏覽器使用情況(隨手安利一下自己的瀏覽器統計工具https://github.com/future-team/cat-browser)。ie6這個貨排名還不太靠后,遑論ie7-9!
要兼容這些特立獨行的文藝青年,react真的有點力不從心了。雖然有一些辦法可以解決一些問題,例如引入es-shims轉換es6語法的不備支持現象。但是整體來說還是不能用的。現身說法,前段時間一個項目使用react來開發,要求兼容ie8,但是react路由的hash值在ie8下面竟然會丟失。。。。最后還是用一些其他方式繞過了。所以jquery還是有存在的必要性的。
要是開發兩套組件,成本還是蠻大的,並且重復的工作量也不小。所以就有個想法能不能開發一個公用組件,jquery和react技術棧都可以使用。剛開始的時候也覺得不太現實,畢竟兩種技術的定位和開發模式存在很大差異。不過空想是沒什么用的,動手實踐一下才是王道。(今天的前言說的有點多了。。。)所以打算實現一個celling組件這個名字還真不好想,就是實現簡單的吸頂、吸底和中間特定條件下的吸頂的一個定位組件。
首先分析一下實現的可能性:
一、分析一下兩者的不同:
1、在於html的渲染:jquery畢竟只是個類庫,具體dom元素還是要html來渲染或者其他方式插入,react通過自己的jsx語法將兩者放在一起通過虛擬dom來渲染
2、操作dom的方式:jquery通過直接操作真實dom來實現需求, react各個組件作為狀態機,需要通過改變狀態重新渲染自己的虛擬dom,通過diff算法決定更新於否
3、事件的綁定處理方式的不同:jquery有自己的一套事件處理系統,react也同樣。概而言之,兩者都是在原生js的基礎上進行了不同封裝而已。
綜上而言,其實不同的主要在於與view相關的部分。但是作為一個相同功能的組件而言邏輯部分是相同的。這說明我們的封裝時完全可以實現的。
二、目前有利的技術:
雖然es6還未正式發布,但是babel的存在已經解決了這個問題。es6提出的一些新特性和語法糖,極大的簡潔了某些繁瑣的寫法。特別是class和繼承屬性的增加,使得es6的繼承已經變得相對優雅一些了。(相關es6可參考阮一峰的文章)
三、實現思路和設計:
原本設想的是抽離一個公共base類出來存放公共邏輯部分,jquery適用的類Jq和react適用的類Re 繼承於該類,各自實現ui相關的部分。如下圖所示(忽略現在已經不會畫類圖的細節):
但是忽然后來想起來剛才提到的react,萬事萬物皆組件。。。。既然都是組件那么他們都繼承於react提供的類Componet。因為多重繼承是不現實的。所以當時就感覺有點暗無天日了。后來經過老司機提醒想起來,有一種模式就好像是為解決其而生的,那就是裝飾者模式。
關於裝飾着模式這里只簡單說兩句,Decorators讓我們能夠在設計時對類、屬性等進行標注和修改成為了可能。Decorators利用了ES5的Object.defineProperty
來實現這一特性。簡而言之裝飾者處理傳入的對象,為其增添一些靜態或者實例方法或屬性。在需要增加屬性的class中通過@的方式來實現注入。修飾者模式具體可以參考http://www.cnblogs.com/whitewolf/p/details-of-ES7-JavaScript-Decorators.html。
現在的實現方式做了改變,react的class繼承於component,jquery的不繼承任何基類。相同的部分通過裝飾者模式注入,如下圖所示:
裝飾者具體實現示例如下:
在具體的class注入公共的方法:
四、具體的實現:
基本思路通了之后,剩下的就是代碼編寫了:
1、options.js:簡單的配置文件,root指定要實現的元素,position:指定top,bottom,middle等方位.classnames.js匹配不同的position,獲取不同的class。代碼如下:
let options = { root:'', position:'top' }; let className = { top:'fix-top', bottom:'fix-bottom', middle:'fix-top' };
2、關於公共Minix的抽離:因為實現組件只是很簡單的定位組件,所以整體邏輯不多。主要增加了一些實例方法,獲取不同定位的class,生成唯一key等,為了統一操作class,這里沒有適用jq的方式,而是自己封裝了一些方法,使得react和jquery通用。
1 import options from './options.js'; 2 import classNames from './className.js'; 3 export default objs=>{ 4 objs.prototype.test= function(){ 5 console.log('test1'); 6 }; 7 objs.prototype.getCName= function(pos = 'top'){ 8 return classNames[pos]; 9 }; 10 objs.prototype.isMiddle= function(pos = 'top'){ 11 return pos == 'middle'; 12 }; 13 /** 14 * 獲取唯一的id 15 * */ 16 objs.prototype.getUniq=function(){ 17 return 'cell'+Math.floor(Math.random()*100); 18 }; 19 /** 20 * 是否有某class 21 * */ 22 objs.prototype.hasClass = function(obj,cls){ 23 return obj.className.match(new RegExp('(\\s|^)' +cls+ '(\\s|$)')); 24 }; 25 26 /** 27 * 增加classs 28 * */ 29 objs.prototype.addClass = function(obj,cls){ 30 if (!this.hasClass(obj, cls)) { 31 obj.className = (obj.className + " " + cls).replace(/\s{2,}/g, " "); 32 } 33 }; 34 /** 35 * 刪除class 36 * */ 37 objs.prototype.removeClass = function(obj,cls){ 38 if (this.hasClass(obj,cls)) { 39 let reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); 40 obj.className = arguments[0].className.replace(reg, ' ').split(" ").join(" "); 41 } 42 }; 43 /** 44 * 取反 45 * @param bool 46 * */ 47 objs.prototype.getInvert = function(isBool){ 48 return !isBool; 49 }; 50 /** 51 * 獲取初始的offsetTop 52 * @param dom 53 * */ 54 objs.prototype.getDTop = function(obj){ 55 return obj.offsetTop; 56 }; 57 }
3、各class的具體實現就比較簡單了,通過@注入minix,然后實現特定的方法。這里以react為例:
1 /** 2 * react 適用 3 * */ 4 import React, {PropTypes,Component} from 'react'; 5 import ReactDom from 'react/lib/ReactDOM'; 6 import CellMin from './utils/CellMixin.js'; 7 import '../css/cell-react.less'; 8 import options from './utils/options.js'; 9 @CellMin 10 class ForReact extends Component{ 11 constructor(props,context) { 12 super(props,context); 13 this.uniquRef = this.getUniq(); 14 } 15 static defaultProps = options; 16 componentDidMount(){ 17 this.isMiddle && this.addEvent(); 18 } 19 render(){ 20 return( 21 <div ref={this.uniquRef} className={ 22 this.getClass() 23 }> 24 {this.props.children} 25 </div> 26 ) 27 } 28 getClass(){ 29 let pos = this.props.position; 30 this.isMiddle = this.isMiddle(pos); 31 this.cls = this.getCName(pos); 32 return !this.isMiddle ? this.cls :''; 33 } 34 /** 35 * 監聽滾動事件 36 * */ 37 addEvent(){ 38 let cellDom = ReactDom.findDOMNode(this.refs[this.uniquRef]); 39 this.isReset = true; 40 let deTop = this.getDTop(cellDom); 41 document.addEventListener("scroll",()=>{ 42 /** 43 * 不再一一列出 44 * */ 45 }) 46 } 47 } 48 module.exports = ForReact;
到這里,適用於jquery和react的celling組件就基本完成了。引入的時候只需要單獨引入需要的版本即可。
總結一下,這種開發方式開始的時候確實不太適應。感覺如果熟練了,應該比開發不同框架的兩套組件要省力。可能的隱患在於本文的組件只是功能很簡單的例子,涉及到的邏輯和操作不是很多,目前看還算可以。但如果是功能比較復雜的組件,是否也可以做到比較徹底的封裝抽離,就有待商榷了。參考文章http://www.cnblogs.com/whitewolf/p/details-of-ES7-JavaScript-Decorators.html
此文還是拋磚,希望能得到大神的意見。最后源碼地址https://github.com/future-team/multiple-celling
轉載請注明出處!!!