首頁 >web前端 >js教程 >vue實踐小結之mvvm學習

vue實踐小結之mvvm學習

小云云
小云云原創
2018-03-12 16:37:141325瀏覽

MVVM是Model-View-ViewModel的簡寫。微軟的WPF帶來了新的技術體驗。本文主要和大家分享vue實作小結之mvvm學習,希望能幫助大家。

1 mvvm 學習

1.1 實作原理

mvvm類別框架的實作原理不複雜,大致如下:

  • ##範本分析得到依賴的屬性

  • 透過某種變動監測手段監測這些依賴的屬性

  • 當屬性變動的時候,觸發對應的directive的處理邏輯即可

實際上,directive的處理邏輯不一定是對view進行操作,例如回報。但是,在mv的思想下,建議對view的操作都集中在directive裡實現

#從最核心上看,mv思想只是一個觀察者模式的具體應用於延展而已

1.2 核心技術點

1.2.1 模板分析

模板分析是比較基礎的,凡是和view相關的基本都會涉及模板,這是原始資料,這裡的關鍵點是模板來源的問題,實際上,它應該可以是任何字串

這裡暗示了框架需要一個模板解析器,不管這個解析器複雜還是簡單,它都處於一個模式:【輸入–> 模板引擎–> 輸出】

於是,mvvm的模板解析器特點如下:

    ##輸入:任何符合規則的字符字串
  • 輸出:需要監聽的data.attr,directive,filter
  • 在設計一個框架的時候,如果想要有更好的可擴展性,則

輸入應該足夠靈活,從來源上來說,模板可以是someDomHere.html(),也可以是動態輸入,那就更有可適用性;從內容上來說,如果引擎可以辨識更高階的語法,那就更有功能性

輸出應該足夠收斂,收斂的意思是有限且規則,像mvvm框架,最後出來的只是directive和filter,具體的處理都集中在這兩個概念中,僅擴展這兩個概念,即可對系統進行擴展

1.2.2 變動監測

在眾多mvvm類框架中,實現變動監測有3種:

    門面方法setter,getter:例如knockout,q。限定可以變動的入口,並且讓入口使用權放給使用者決定。
  1. 利用defineProperty:例如vue,avalon。本質上也是setter,getter,但是沒有把入口使用權放給用戶來決定。
  2. dirty check:例如angular。 angular的研究已經夠多了,這裡也不贅述了。
1

#2

3

##4

#5

6

7

8

9

10

##11

<span class="comment">//方式1 vs. 方式2</span>
<span class="comment">//方式1:</span>
vm.<span class="variable">$set</span>(aaa, <span class="number">1</span>);    <span class="comment">//会触发变动逻辑</span>
vm._data.aaa = <span class="number">2</span>;   <span class="comment">//不会触发变动逻辑,不过这不是框架希望的操作,可以被hack</span>
vm.<span class="variable">$get</span>(aaa);       <span class="comment">//2</span>
<span class="comment">//方式2:</span>
vm.aaa = <span class="number">1</span>;         <span class="comment">//一定会触发变动逻辑</span>
vm._data.aaa = <span class="number">2</span>;   <span class="comment">//也可以找到内部的data进行修改,但是没用</span>
vm.aaa;             <span class="comment">//1</span>


#

1.2.3 小結與延伸

對一類複雜並且常見的問題進行分析,解耦,抽象,在實踐的過程中獲得廣泛的認可,那就形成了一種模式,mvvm也是一種模式,它不一定叫mvvm模式,這也不是筆者能決定的

對於這個模式的核心,筆者理解如下:系統根據配置得到了對某些資料來源的某些處理規則,當資料來源變動時就會引發對應的處理規則。模式的擴展是雙向性,這由系統實作來決定,當符合某些規則的時候,可以對資料來源進行更新。

我們跳脫view的概念禁錮,聯想實現一個監控系統,其實這個模式非常適合用在監控系統上面。

一般的監控系統的處理邏輯是:由收集來源對監控資料進行收集整理,然後儲存到資料庫中,監控系統即時監控資料來源,繪製即時的圖線(回饋),當資料來源當發生了符合某些規則的變動時,就會觸發對應的動作,例如警報。

如何實現這個系統,讓系統有更高的擴充性?參考mvvm模式,可以這樣:

收集系統獨立於監控系統,各不相同,暫且不論。監控系統透過某些設定檔取得需要監控的資料來源與對應的處理邏輯規則,當資料來源變更時觸發對應的處理。

依照mvvm模式,進行一些抽象。

  • 資料來源不一定限定在資料庫中,他可以在任何地方,只需要係統可以透過某些可設定的規則來取得

  • 處理規則進行抽象,讓它更容易被擴展,例如發郵件,發短信,發微信,發qq訊息等等

對應前端的mvvm框架,模板就是配置文件,directive就是處理規則,data對應資料來源。

  • 當系統需要新增一個資料來源的時候,只需要更新設定文件,讓系統讀取即可啟動資料監控

  • 當需要新增一個處理規則的時候,可以透過一個熱插拔的處理規則插件系統,擴展一個新的處理規則,再更新配置文件,系統即可接受新的處理規則

2 vue實作

vue介紹就不用了,太多資源了。這裡講述vue實踐過程中的一些收穫

2.1 組織結構

#1

2

3

4

5

6

7

8

9

10

11

12

13
#14

##+ src

     + -- common

           +-- vue

                

#                +-- filters

              -- vue.js

                +-- vue.ext.js

     +-- #  

                 +-- index.js

                 +-- vue.ext.js

                 +-- xxx.mixin.js

2.2 Vue扩展

vue的扩展非常方便,与vue相关的资源都放置在src/common/vue/下面,比如coms(组件),directive,filter

src/common/vue/vue.ext.js是对vue进行全局公共的扩展,对于所有页面共有的扩展放在这个文件下面,内容如下:

可以看到,扩展vue库本身有4个扩展点:

  • 扩展Vue库的全局方法/属性,方式:Vue.xxx = …

  • 扩展Vue实例的方法/属性,方式:Vue.prototype = …

  • 扩展directive,方式:Vue.directive(‘directiveName’, options);

  • 扩展filter,方式:Vue.filter(‘filterName’, function(){});

对于页面单独需要的扩展,集中在src/pages/pageName/vue.ext.js里面,形式与全局的vue.ext.js一样

在实例化Vue的过程中也有许多可以扩展与优化的地方,在实践过程中只是应用了mixin功能,其他的可以慢慢深入

mixin的作用是在实例化Vue的时候混入一些功能,它可以混入许多特性,格式与实例化Vue时用到的option格式一样,比如index页面的mixin.js的内容如下:

这个mixin混入了两个方法,多个Vue实例共享的options可以放置到mixin中,从而避免了代码重,比如在实例化Vue的时候这样使用mixin:

可以看到mixin是个数组,因此可以同时使用多个mixin

实际上这里的mixin主要不是为了避免代码重复(实践的时候只是这样用),mixin是一种模式,一个mixin内聚了实现一项功能的方法/属性集合,在定义/生成实例的时候,通过混入mixin就可以让该实例拥有某项功能,归根结底是组合vs继承问题的产物

2.3 vue组件插入问题

2.3.1 首屏

对于首屏的vue组件,直接把模板放在主页面中即可,初始化的时候只需要把el参数传入,Vue就会用el的html作为模板来初始化Vue实例:

这里需要注意的是在模板中不能使用{{}},否则在还没初始化之前,页面会显示奇怪的东西,比如:

1

2

3

4

5

6

7

<p>hello, {{name}}</p>      <!--初始化前,页面会直接展示hello, {{name}}-->
<img src=<span class="s
tring">"{{imgSrc}}"</span> />    <!--初始化前,会报错,can not find http:<span class="comment">//xxx.com/{{imgSrc}}--></span>
 
<!--正确的写法:-->
<p v-text=<span class="string">"&#39;hello, &#39;+name"</span>>hello</p>
<img v-attr=<span class="string">"src: imgSrc"</span> />

 

{{}} 只是一个语法糖,不建议使用

2.3.2 非首屏

对于非首屏的组件,使用vue的方式和原始方式差不多,先生成节点,然后append,譬如:

el参数可以接收query string,也可以直接是一个dom节点,如果是dom节点则直接编译dom的内容。如果dom节点不在文档树中,则利用vueObj.$appendTo方法将vue实例的根节点插入到文档树中

上面这种方式是在页面中没有组件的【坑】的情况下使用的,如果页面为组件留了【坑】,比如:

1

2

0510d38451755f656b967a56ae76d68aclass54bdf357c58b8a65c66d7c19c8e4d114=e7b5d65d577098ec09191daa17885c87"hotRecord"54bdf357c58b8a65c66d7c19c8e4d114 id=e7b5d65d577098ec09191daa17885c87"js-hotRecord"54bdf357c58b8a65c66d7c19c8e4d114>4d7ab0de9a42de71c682b0860bad1410

 

那么,我们可以这样初始化vue实例:

利用template参数传入模板,并指定el,那么vue实例在初始化之后就会自动把内容插入到el中

通过vue实现组件的主要核心也就这些,更方便的组件写法也只是对这些进行封装

2.4 自定义 directive

在vue中自定义directive是非常简单明了的,要自定义一个directive,可以注册3个钩子函数:

  • bind:仅调用一次,当指令第一次绑定元素的时候。

  • update:第一次调用是在 bind之后,用的是初始值;以后每当绑定的值发生变化就会被调用,新值与旧值作为参数。

  • unbind:仅调用一次,当指令解绑元素的时候。

下面简单介绍一个自定义directive——lazyload:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

<span class="keyword">function</span> addSrc(){}
<span class="keyword">function</span> load(){}
 
module.exports = {
    bind: <span class="keyword">function</span>() {
        <span class="keyword">if</span> (!hasBind) { <span class="comment">//全局事件只绑定一次</span>
            hasBind = <span class="keyword">true</span>;
            (document.querySelector(<span class="string">&#39;.z-scroller&#39;</span>) || window).addEventListener(<span class="string">&#39;scroll&#39;</span>, T.debounce(load, <span class="number">100</span>), <span class="keyword">false</span>);
        }
        <span class="comment">//这里也可以使用data属性来获取</span>
        <span class="keyword">var</span> defaultSrc = <span class="keyword">this</span>.el.getAttribute(<span class="string">&#39;data-defaultsrc&#39;</span>);
        <span class="keyword">if</span> (defaultSrc) addSrc(<span class="keyword">this</span>.el, defaultSrc);    <span class="comment">//先使用默认图片</span>
    },
    update: <span class="keyword">function</span>(src) {
        <span class="comment">//directive初始化时,会调用一次bind和update,bind没有传入src,只有update才会传入src</span>
        <span class="comment">//因此只能在update这里拿到需要lazyload的src</span>
        <span class="comment">//lazyload不允许修改src,这里限制只会执行一次update,防止src被修改造成的影响</span>
        <span class="comment">//注:接受src改变可以实现,只是需要一些复杂的处理,这里为了简单起见不让src改变</span>
        <span class="keyword">if</span> (<span class="keyword">this</span>.init) <span class="keyword">return</span>;  
        <span class="keyword">this</span>.init = <span class="keyword">true</span>;
 
        <span class="comment">//如果图片已经加载了,就不需要注册了,这里也可以使用data属性来区分</span>
        <span class="keyword">var</span> isLoad = parseInt(<span class="keyword">this</span>.el.getAttribute(<span class="string">&#39;data-isload&#39;</span>));
        <span class="keyword">if</span> (isLoad) <span class="keyword">return</span>;
 
        <span class="comment">//注册需要lazyload的图片</span>
        <span class="keyword">list</span>[index++] = <span class="keyword">this</span>;
        <span class="keyword">list</span>[index++] = src;
    }
    <span class="comment">//这里有一个最大的问题:由于有local的存在,会创建两个一模一样的lazyload directive</span>
    <span class="comment">//按理说应该定义一个unbind,但是在unbind中找到并除掉local创建出来的lazyload directive会比较麻烦</span>
    <span class="comment">//因此在load函数里面做了一个处理:如果发现需要lazyload的节点不在文档树中,则剔除掉这个lazyload</span>
    <span class="comment">//通过这个直接省掉了unbind函数</span>
};

 

自訂filter也很簡單,只是定義一個處理函數而已,這裡就不多介紹了

2.5 實踐過程中的痛點與小技巧

2.5.1 沒有事件代理

用習慣了事件代理,突然沒有了會有點不習慣,但是回頭想想,事件代理真的很重要嗎?還是說我們只是習慣了事件代理而已?

透過vue註冊相同的事件並不費事。另一個問題,只要事件不多,大約不超過50,100,也不至於耗掉很大的內存,因此有時候還真不需要事件代理。如果真的需要,也只是實作一個contain方法而已

2.5.2 沒有if-else的奇怪

最初看到下面的程式碼真的會覺得很奇怪

1

2

3

#2b3ffebfe4fa2a05dc1f7448b6b7c948if54bdf357c58b8a65c66d7c19c8e4d114=fab9ab63db7b134c286b50796f2abfe1"hasTitle"54bdf357c58b8a65c66d7c19c8e4d114>xxx39528cedfa926ea0c01e69ef5b2ea9b0

a2416441a4809ad4aa10cebcb3d99644if54bdf357c58b8a65c66d7c19c8e4d114=fab9ab63db7b134c286b50796f2abfe1"!hasTitle"54bdf357c58b8a65c66d7c19c8e4d114>xxx94b3e26ee717c64999d7867364b1b4a3

 

#2.5.3 單值

雖然vue有語法解析器,可以在directive的值中使用表達式,但是當出現一個複雜的表達式時,會污染模板,讓程式碼可讀性變得很差,又或者,表達式完成不了這個任務的時候。

因此,在mvvm實踐的過程中,深深發現,利用單值(最多只用一個?:表達式)來寫模板會讓程式碼變得很清晰,更加可讀,增加程式碼的可維護性,而且這也更符合mvvm的核心思想:f(state) = view

有些函式庫連語法解析器都沒有,例如q,但也能很好的工作。

那麼,複雜的操作放在哪裡呢?

  • 對於不會變的值來說,也就是常數,要在初始化之前完成處理

  • #對於會變的值來說,把複雜的操作放在filter裡面,在filter裡面不僅可以進行複雜處理,甚至可以同時應用到其他字段,這不完全等同於computed attribute

2.5.4 替代$(document).on

用jquery/zepto的時候,習慣了用$(document).on來充當一個全局的事件代理,在使用vue的時候,需要拋棄zepto,因此需要解決這個問題

因為vue實例本身就有event功能,因此這裡解決的辦法是建立一個全域的空vue對象,把它當作全域的事件代理:

##1

2

3

4

#5

6

7

8

9

89e7ad2b99889e4d86b051f40860f447//common/vue/vue.ext.js 回頭看前面對該文件的介紹可以看到這句話54bdf357c58b8a65c66d7c19c8e4d114

Vue.noopVue = bd51fd4180b618149e9b74046b0a37ednew54bdf357c58b8a65c66d7c19c8e4d114 Vue({});

############################################################################################################################################################################### ## ######89e7ad2b99889e4d86b051f40860f447//a.js54bdf357c58b8a65c66d7c19c8e4d114######Vue.noopVue.d9df1c7814ea394f861c6a0ed66fe7ab$on494c0df226525cc046cf4930a65bbd6f(fab9ab63db7b134c286b50796f2abfe1'someEvent'54bdf357c58b8a65c66d7c19c8e4d114, bd51fd4180b618149e9b74046b0a37edfunction54bdf357c58b8a65c66d7c19c8e4d114() {});####### ######89e7ad2b99889e4d86b051f40860f447//b.js54bdf357c58b8a65c66d7c19c8e4d114######Vue.noopVue.d9df1c7814ea394f861c6a0ed66fe7ab$emit
3 總結

雖然,最後在付出產出比權衡中放棄了對現有專案的vue改造,但這並不妨礙我們研究mvvm類別框架

mvvm模式還是值得我們去深入學習的,而在實踐中,我們也能學習到許多

用一種不一樣的思想和思維去開發的體驗也會令我們在看待問題,處理問題的道路上有所收穫。

相關推薦:

Angularjs如何實作mvvm式選項卡?案例+程式碼

js實作一個簡單的MVVM框架範例分享

什麼是MVVM架構和資料綁定?

以上是vue實踐小結之mvvm學習的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn