天天看點

從DOM操作看Vue&React的前端元件化,順帶補齊React的demo

前言

接上文:談談我對前端元件化中“元件”的了解,順帶寫個Vue與React的demo

上次寫完部落格後,有朋友反應第一内容有點深,看着迷迷糊糊;第二是感覺沒什麼使用場景,太過業務化,還不如直接寫Vue&react的源碼分析,我感覺這裡有必要說下我的認識。

首先,要寫源碼分析很難,第一是他本來就很難,是以一般我們是想了解他實作的思路而不是代碼;

第二每個開發者有自己發風格,是以你要徹底讀懂一個人的代碼不容易,除非你是帶着當時作者同樣的問題不斷的尋找解決方案,不斷的重構,才可能了解使用者的意圖。

我們上一次做的事情其實就是根據自己實際的工作經驗做了和外面架構類似的事情,雖然代碼的健壯、優雅程度跟不上,但卻和其它作者一樣為解決同樣的問題思考得出的方案,上次做的太晚了,後面就草草結束,事實上在我Demo過程中發現一個事實:業務代碼都是差不多的,隻是一些細節點不一樣,是以決定産品品質的依舊是開發者的業務代碼能力,架構隻是助力而已。

不能了解作者的意圖,不能提高本身的程式設計水準,就算用上了React&Vue這類元件化的架構,也組織不好代碼;事實上這類代碼因為是面向大型應用的,反而更考驗一個人的架構能力,是以大家要多注重内在修養的提升哦。

下面我們進入今天的正題,這裡依舊提供一些幫助了解的資料:

github

代碼位址:https://github.com/yexiaochai/module/

示範位址:http://yexiaochai.github.io/module/me/index.html

如果對文中的一些代碼比較疑惑,可以對比着看看這些文章:

【一次面試】再談javascript中的繼承

【移動前端開發實踐】從無到有(統計、請求、MVC、子產品化)H5開發須知

【元件化開發】前端進階篇之如何編寫可維護可更新的代碼

預覽

使用Vue的思考

因為第一個demo是Vue的,React應該也類似,對比之前的代碼發現一個重要差異是:

DOM操作真的完全沒有了!!!

對,是完全沒有DOM操作了,這個是很牛逼的一件事情,因為我覺得有兩個地方要擺脫DOM操作很難:

① 我的元件應該放到哪個容器内,我需要一個定位的元素,比如:

1 this.sortModule = new SortModule({

2     view: this,

3     selector: '.js_sort_wrapper',

4     sortEntity: this.sortEntity

5 });

明确的告訴了元件所屬的容器

② 我比較疑惑像這類清單類型的事件該如何處理,因為一些必要參數是根據event擷取的,比如:

1 listItemClick: function (e) {

2     var el = $(e.currentTarget);

3     //根據el做一些事情

4 }

關于這個Vue的作者認為應該将事件處理程式内聯,做顯示聲明:

複制代碼

你可能注意到這種事件監聽的方式違背了傳統理念 “separation of concern”。不必擔心,

因為所有的 Vue.js 事件處理方法和表達式都嚴格綁定在目前視圖的 ViewModel 上,它不會導緻任何維護困難。實際上,使用 v-on 有幾個好處:

掃一眼 HTML 模闆便能輕松定位在 JavaScript 代碼裡對應的方法。

因為你無須在 JavaScript 裡手動綁定事件,你的 ViewModel 代碼可以是非常純粹的邏輯,和 DOM 完全解耦,更易于測試。

當一個 ViewModel 被銷毀時,所有的事件處理器都會自動被删除。你無須擔心如何自己清理它們。

<button v-on:click="say('hello!', $event)">Submit</button>

1 methods: {

2   say: function (msg, event) {

3     // 現在我們可以通路原生事件對象

4     event.preventDefault()

5   }

6 }

還有種常用的操作,比如radioList,點選目前選項便選擇項目,我們一般的做法是這樣的:

1 setIndex: function (i) {

2     this.index = i;

3     this.$('li').removeClass(this.curClass);

4     this.$('li[data-index="' + i + '"]').addClass(this.curClass);

5 }

這樣做比較簡單,但是會有一個問題,便是資料與dom表現的流程變了,正确的流程是index 變了,dom便根據資料做更新,比如Vue:

3     //這部分邏輯Vue會自動實作

4     //this.$('li').removeClass(this.curClass);

5     //this.$('li[data-index="' + i + '"]').addClass(this.curClass);

之前,不考慮性能,我們會直接根據資料重新渲染整個清單,就為一個簡單的選中功能,而Vue&React卻做到了局部渲染,這個是否牛逼,我相信這個将會是一個核心算法部分,後面有時間一定要深入了解。

根據以上局部解讀,我們得到一個結論,隻要達成兩個條件,就能擺脫DOM操作:

① 知道元件所處容器

② 根據資料渲染頁面

PS:我們這裡是很簡單的一環,沒有考慮元件嵌套,元件通信等過于複雜的問題

那麼如果達成了以上條件,我們能否做到業務邏輯中不包含dom操作呢?我們下面就來試試。

如何擺脫DOM操作

這裡真的是demo類嘗試,思維驗證,便不使用之前過于複雜的業務邏輯了,這裡将me目錄拷貝一塊出來,依舊以原來的代碼做底層依賴,隻要清單與頂部排序部分功能,這裡為了簡化實作,保持代碼重用,我們這裡直接想将entity子產品複用,要求data中的對象必須是一個entity執行個體,這裡第一步是抽象出來了list module子產品,于是主要制器變成這樣了,事實上這個時候已經沒dom操作了:

 1 initEntity: function () {

 2     //執行個體化排序的導航欄的實體

 3     this.sortEntity = new SortEntity();

 4     this.sortEntity.subscribe(this.renderList, this);

 5 },

 6 

 7 initModule: function () {

 8     //view為注入給元件的根元素

 9     //selector為元件将要顯示的容器

10     //sortEntity為注入給元件的資料實體,做通信用

11     //這個module在資料顯示後會自動展示

12     this.sortModule = new SortModule({

13         view: this,

14         selector: '.js_sort_wrapper',

15         sortEntity: this.sortEntity

16     });

17     this.listModule = new ListModule({

18         view: this,

19         selector: '.js_list_wrapper',

20         entity: this.sortEntity

21     });

22 },

23 

24 propertys: function ($super) {

25     $super();

26 

27     this.initEntity();

28     this.initModule();

29     this.viewId = 'list';

30     this.template = layoutHtml;

31     this.events = {};

32 }

這裡簡單看看清單元件的實作,其實就是将原來根View的代碼換個位置:

 View Code

就這種簡單的改變,貌似便擺脫了DOM操作,頁面所有的狀态事實上是可以做到由資料控制的,但是這裡沒有形成“标簽化”,似乎不太好,于是我們來試試是否能改造為标簽化的代碼。

我們這裡的業務代碼(module與entity)沒有什麼需要改動的,這裡主要在底層做改造,這裡在我看來是提供了一種“文法糖”的東西,這裡的具體概念後續閱讀Vue源碼再深入了解,這裡先照着做,這裡看結果想實作,也是我們常用的一種設計方案,首先我們的index程式設計了這個樣子:

1 <article class="cm-page page-list" id="main">

2     <div class="js_sort_wrapper sort-bar-wrapper">

3         <mySortBar :entity="sortEntity"></mySortBar>

4     </div>

5     <myList :entity="listEntity" :sort="sort"></myList>

6 </article>

 1 (function () {

 2     require.config({

 3         paths: {

 4             'text': 'libs/require.text',

 5 

 6             'AbstractView': 'js/view',

 7             'AbstractEntity': 'js/entity',

 8             'ModuleView': 'js/module'

 9         }

10     });

11 

12     require(['pages/list.label'], function (List) {

13         var list = new List();

14         list.show();

15     });

16 })();

PS:裡面的js鈎子基本無用了

這裡标簽化帶來的好處是,根View中有一段執行個體代碼可以不用與選擇器映射了,比如這個:

2     //view: this,

3     //selector: '.js_sort_wrapper',

4     //sortEntity: this.sortEntity

因為處于元件中,其中所處位置已經定了,view執行個體或者entity執行個體全部是跟View顯示注入的,這裡根View中參考Vue的使用,新增一個components與components與entities屬性,然後增加一$watch對象。

大家寫底層架構時,私有屬性或者方法使用_method的方式,如果是要釋放的可以是$method這種,一定要“特殊化”防止被執行個體或者繼承覆寫

 1 define([

 2     'AbstractView', 'pages/en.sort', 'pages/mod.sort', 'pages/mod.list'

 3 ], function (AbstractView, SortEntity, SortModule, ListModule) {

 4     return _.inherit(AbstractView, {

 5         propertys: function ($super) {

 6             $super();

 7             this.$entities = {

 8                 sortEntity: SortEntity

 9             };

10             this.$components = {

11                 mySortBar: SortModule,

12                 listModule: ListModule

13             };

14             this.$watch = {

15 

16             };

17             this.viewId = 'list';

18             this.template = layoutHtml;

19             this.events = {};

20         }

22 });

他這種做法,需要元件在顯示後架構底層将剛剛的業務代碼實作,使用元件生成的html代碼将原來标簽的占位符給替換掉。

這裡在元件也需要明示根View需要注入什麼給自己:

PS:事實上這個可以不寫,寫了對後續屬性的計算有好處

//記錄需要根View注入的屬性

props:[sortEntity],

PS:底層什麼時候執行替換這個是有一定時機的,我們這裡暫時放到根View展示後,這裡更好的實作,後續我們在Vue與React中去找尋

因為我們這裡是demo類實作,為降低難度,我們為每一個元件動态增加一個div包裹層,于是,我們在跟View中,在View展示後,我們另外多加一段邏輯:

1 //執行個體化實體,後面要用

2 this._initEntity();

3 //新增标簽邏輯

4 this._initComponent();

然後将實體與元件的執行個體化放到架構底層,這裡實體的執行個體化比較簡單(如果有特殊資料需求再說,這裡隻考慮最簡單情況):

1 _initEntity: function() {

2     var key, entities = this.$entities;

3     //這裡沒有做特殊化,需要注意

4     for(key in entities) {

5         this[key] = new entities[key]();

6     }

7 },

而執行個體化元件的工作複雜許多,因為他需要将頁面中的自定義标簽替換掉,還需要完成很多屬性注入操作:

1 _initComponent: function() {

2     var key, components = this.$components;

3     for(key in components) {

4         //這裡執行個體化的過程有點複雜,首先将頁面的标簽做一個替換

5         var s = ''

 1 _initComponent: function() {

 2     var key, components = this.$components;

 3     var el, attributes, attr, param, clazz, i, len, tmp, id, name;

 4 

 5     //這裡執行個體化的過程有點複雜,首先将頁面的标簽做一個替換

 6     for(key in components) {

 7         param = {};

 8         clazz = components[key];

 9         //由原型鍊上擷取根元素要注入給子元件的屬性(這個實作好像不太好)

10         attributes = clazz.prototype.props;

12         //首先擷取标簽dom元素,因為html是不區分大小寫的,這裡将标簽小寫

13         el = this.$(key.toLowerCase());

14         if(!el[0]) continue;

16         if(attributes) {

17             for (i = 0, len = attributes.length; i < len; i++) {

18                 attr = attributes[i];

19                 name = el.attr(':' + attr);

20                 param[attr] = this[name] || name;

21             }

22         }

24         //建立一個空div去替換原來的标簽

25         id = _.uniqueId('componenent-id-');

26         tmp = $('<div component-name="' + key + '" id="' + id + '"></div>');

27         tmp.insertBefore(el);

28         el.remove();

29         param.selector = '#' + id;

30         param.view = this;

31         this[key] = new components[key](param);

32     }

33 

34 },

于是這個标簽便能正常展示了:

3         <mySortBar :entity="sortEntity" :myname="111"></mySortBar>

5     <myList :entity="sortEntity" :sort="sort"></myList>

後面想要把這段代碼去掉也十分輕易,我這裡就不進行了:

1 'click .js_sort_item li ': function (e) {

3     var sort = el.attr('data-sort');

4     this.entity['set' + sort]();

總結

這裡首先根據上次Vue的demo産生了一些思考,并且以簡單的demo驗證了這些思考,樓主在使用過程中發現Vue很多好的點子,後續應該會深入研究,并且以實際項目入手,這裡回到今天的正題,我們使用React實作上次遺留的demo。

React的實作

在我最初接觸React的時候,React Native還沒出現,是以很多人對React的關注不高,當時做移動端直接放棄了angular,以體量來說,React也不在我們的考慮範圍内,誰知道野心勃勃的Facebook搞出了React Native,讓React徹底的跟着火了一把,事實上隻要有能力以JavaScript統一Native UI的公司,這個實作就算換個架構依舊會火,雖然都已經這麼火了,但是React的文檔卻不怎樣,我後續也有試水React Native的興趣,屆時再與各位分享。

PS:根據之前的回報,這次demo稍微做簡單點,也隻包含頂部導航和清單即可:

他這個文法據說是讓開發變得更簡單了,我反正是不喜歡,這裡有個不好的地方,之前資料實體全部是在根View上執行個體化的,然後注入給子View,React這裡屬性完全獨享了,現在我觸發了狀态的改變,如何通知到list元件重新渲染排序呢?

React 元件通信

這裡React子元件之間如何通信暫沒有研究出來,是以将需要通信的資料做到了父元件中

react的中文文檔整理較差,很多資料找不到,jsx文法比較怪異,不是所有人能接受,我去找模闆循環時候壓根就沒找到,是以jsx有個特點,他讓你不得不去拆分你的元件,在我寫React代碼中,感覺React代碼控制力度要重一點,但是如果沒有良好的架構能力,我可以毫不誇張的說,你依舊寫不好業務代碼。

至于React與Vue的優劣,這個方面見仁見智吧,好了今天的文章到此為止。

後續我們可能會深入分析下Vue的實作,在React Native上做深入,有興趣的同學可以持續關注。

文章有任何不足錯誤,請您提出,因為小钗也是第二次使用React寫demo,如果使用不當請多包涵

本文轉自葉小钗部落格園部落格,原文連結:http://www.cnblogs.com/yexiaochai/p/5515264.html,如需轉載請自行聯系原作者

繼續閱讀