React jQuery公用組件開發模式及實現


  目前較為流行的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

  轉載請注明出處!!!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM