mnv*框架开发时代
2019獨角獸企業重金招聘Python工程師標準>>>
原文出處:極限前端
??當下前端開發框架設計顯然已經在mvvm方式上又發展了一步,virtual dom 提出不久,使用前端代碼來調用native的思路就開始被實踐。相信大家也知道是什么東西。到了今天,我們不得不承認,mnv* 框架開發時代已經到來。
??mnv是什么,具體可以這么理解,model-Native-View-*,而后面的則可以認為是 virtual dom 或 mvvm 中的 ViewModel,或者我們也可以自己使用controller來實現的調用方式。想想這樣定義是非常合適的。相比之前的不同,就是用 nativeView代替了 htmlView。那么我們再看看下從dom api 到 mnv*,我們為什么會看到這樣的變化。
一、dom交互
??在此之前不得不提下之前的dom交互框架,就是直接選擇找到特定的dom進行操作,思路十分直接也很實用,通過dom交互框架,相比JavaScript原生API,我們可以比較高效的處理dom的改變和事件綁定了,這種高效的方式給我們到來了效率上的提高,但是頁面復雜了就不好處理了。
??隨著ajax技術的盛行,SPA應用開始被廣泛運用。SPA的引入將整個應用的內容都在一個頁面中進行異步交互。這樣,原有的dom交互方式開發就顯得不好管理,例如某SPA頁面上交互和異步加載的內容很多,我們的做法是每一次請求渲染后做事件綁定,異步請求后再做另一部分事件綁定,后面以此類推。當所有異步頁面全部調用完成,頁面上的綁定將變得十分混亂,各種元素綁定,渲染后的視圖內容邏輯不清,又不得不聲明各種變量保存每次異步加載時返回的數據,因為頁面交互需要對這些數據做操作,最后寫完,項目代碼就成了一鍋粥。
二、前端mvc
??為了更方便的統一管理頁面上的事件、數據和視圖內容,就有了早期mvc的框架設計。mvc可以認為是一種設計模式,其基本思路是將dom交互的過程分為調用事件、獲取數據、管理視圖。即將之前所有的事件、數據、視圖分別統一管理。用model來存放數據請求和數據操作,視圖來存放對視圖的操作和改變,controller用來管理各種事件綁定。
??例如,SPA中的每個異步頁面可以看成是一個組件,之前的做法是每個組件獨立完成自己的數據請求操作、渲染和數據綁定,但是組件多了,每個組件自己去做就比較混亂,邏輯比較混亂。到了mvc里面,所有的組件數據請求、渲染、數據綁定都到一個統一的model、view、controller注冊管理。后面的操作我們就不在管你有多少個組件了,你要調用必須要通過統一的model、view、controller來調。通俗來說就像是組件交出了自己控制權到一個統一的地方注冊調用,這樣就方便了很多,相信大家都已經了解過,這里就省篇幅不舉例了。
三、 前端mvp
??mvp可以跟mvp對照起來看,而且我們也很少專門去關注它。和mvc一樣,mvc的M就是 Model, V就是View,而P,則代表Presenter,它與Controller有點相似。不同的是,在mvc中V會直接展現M,而在mvp中V會把所有的任務都委托給P。V和P會互相持有reference,因此可以互相調用。
例如我們可以在MVC代碼上做一點改變,寫成這樣:
<div controller="Controller.vp" id="text">html</div> var Controller = new Controller(); Controller['vp']= new VP({$el: $('text'),click: fn(e){console.log(self.$el.html());},mouseenter: function(e){console.debug(self.$el.html());},mouseleave: function(e){console.info(self.$el.html());} });??幾個好處,這樣將view和Controller的引用關聯了起來,而MVC一般是通過事件監聽或觀察者的異步方式來實現的,我們可以在任意地方定義注冊監聽事件都不會有問題,這樣監聽的事件和觸發這個事件的html元素脫離了引用,當應用復雜起來后要維護dom的交互邏輯就比較麻煩了。而mvp提供了一個簡單的引用,將元素對應的操作于對應的presenter關聯起來。我們要查詢元素對應的controller時只要通過Controller.vp就可以直接調用了,其實這個時候就和mvvm的定義方式有點類似了,不是嗎?
四、前端mvvm
??mvvm概念可以認為是一個自動化的presenter,也這個時候進一步弱化了C層,任何操作都通過viewModel來驅動。Controller最終在頁面上的行為通過directives的形式體現,通過對directives的識別來注冊事件,這樣管理起來就更清晰了。看一個mvvm框架定義的例子。
<form action="" id="form"><label for="text" q-html="label"></label><input type="text" q-value="value" q-model="value" q-mydo="number | getValue"><button q-on="click: submit"></button> </form> let viewModel = new VM({$el: '#form',data:{label: '用戶名',value: '輸入初始值',number: 0},method:{submit(){// doSubmit}},directive:{mydo(value){console.log(value);}},filter:{getValue(){reutrn value ++;}} })??和MVP的定義比較,有點類似,mvvm設計一個很大的好處是將mvc中controller中的controller注冊到相對應的元素中,讓我們后期維護時很快定位,免去了查看controller中event列表的工作,而且初始化后自動做數據綁定,能將頁面中所有同類操作復用,大大節省了我們自己寫代碼做綁定的時間。這段代碼中初始化時自動幫我們就做了數值填充、數據雙向綁定、事件綁定的事情。那么框架怎樣幫我做的呢。我們來看下new VM做了哪些事情:這里傳入了元素、數據、方法列表、自定義directive列表,首先程序找到這個元素,開始對這個元素的屬性節點進行遍歷,一旦遍歷到屬性名稱含有q-開頭的屬性是,認為是mvvm框架自定義的屬性,然后會對屬性的指進行特殊處理;例如遍歷到q-html="label"時,將data中的label值賦給這個元素的innerHTML;如果遍歷到q-on="click: submit"時,將這這個元素上綁定click事件,事件回調函數為submit;也可以自定義q-mydo的指令,遍歷到該節點屬性是,調用directive中的mydo方法,輸入參數為data中的getValue方法返回的值,getValue輸入參數為number值,這里的getValue被稱為過濾器。
??這里要知道的是q-開頭的屬性指令是框架約定的,不同的框架約定的不一樣,例如ng-、v-、ms-,這些大家也都見過或用過。這里viewModel創建進行綁定的原理就這么簡單,按照這個思路去擴充,就可以自己寫一個mvvm框架。當然完整的框架涉及東西多的多,含有豐富的directive、filter、表達式、vm完善的api和甚至一些兼容性處理等。
??總結來說從mvc到mvp,然后到mvvm,前端設計模式仍然是向著易實現、易維護、易擴展的基本方向發展的。但目前前端各類框架也已經成熟并開始版本迭代。但是,這還沒有結束,我們依然沒有脫離dom編程的基本套路,一次次框架的改進只是提高了我們的開發效率,但是dom元素的效率仍沒有得到本質的提升。
五、前端virtual dom
??為了改進dom交互的效率,或者說是盡量減少dom交互的次數,virtual dom的概念當下十分盛行,目前圈內各種大小團隊紛紛投入項目使用。因為viewModel的改變最終還是要實時操作dom來刷新view層,而dom對象的操作相對于JavaScript對象的操作仍然是要慢些。原因很簡單,dom節點對象的內置屬性很多,就創建一個dom對象而言,dom的創建需要處理各種內置屬性的初始化,而如果使用JavaScript對象來描述就簡單了。
??使用virtual dom,頁面的渲染過程不再是數據直接通過前端模板渲染到頁面,也不是初始化viewModel進行頁面模板填充和事件綁定,而是通過dom衍生描述語法(這為什么稱為DOM衍生描述語法,通常我們通過html來描述,但是目前一些框架是通過非標準的html的方式描述的,定義的一套迎合自己框架的方式,其實使用html也是可以的)解析生成virtual dom,頁面交互變成了是修改virtual dom,然后將virtual dom的改變反映到htmlView層上。
??例如以下結構:
<ul id="ui-list"><li class="ui-list=item">1</li><li class="ui-list-item">2</li><li class="ui-list-item">3</li> </ul>??可以使用如下javascript來表示
var element = {tagName: 'ul',props: {id: 'ui-list'},children: [{tagName: 'li', props: {class: 'ui-list-item'}, children: ["1"]},{tagName: 'li', props: {class: 'ui-list-item'}, children: ["2"]},{tagName: 'li', props: {class: 'ui-list-item'}, children: ["3"]},] }??如果javascript對象children屬性第三個元素要被移除,同時,添加一個class為ui-list-item2的li節點,則首先需要對javascript對象進行修改記錄所有的操作,最后將修改的vitual dom變化反映到頁面上:
<ul id="ui-list"><li class="ui-list=item">1</li><li class="ui-list-item">2</li><li class="ui-list-item2"></li> </ul>??這里的javascript對象就相當于virtual dom,用戶的某個交互操作可能導致dom的多個地方,如果沒有vitual dom,那可能就要進行多次dom操作,virtual dom則可以將多個用戶交互操作反映在virtual dom上,最后做的virtual dom DIFF算法然后再dispatch到頁面view層上。相對于mvvm,在頁面初始化渲染階段,也避免了掃面節點,解析directives,要知道這些操作都是dom操作,使用virtual dom顯然能將頁面渲染速度提高不少。
六、前端 mnv*
??如果說vitual dom減少了dom的交互次數,那么mnv*想要做的一件事情就是完全拋棄使用dom,那樣就只能在view層做改進了,使用nativeView來代替目前html的view,而交互邏輯依然可以使用viewModel、virtual Dom或者controller來實現,具體就看實現的方式了。
??要做到NativeView的操作,這里與之前不同之處就是調用時通過衍生HTML語法通過解釋器執行nativeView的渲染,這是就需要在native和衍生HTML語法之間添加一層解釋器來解析現有的view描述語法了。比如我們看一個渲染Native的例子:
// iOSimport React, {Component, } from 'react'; import {TabBarIOS, NavigatorIOS } from 'react-native';class App extends Component {render() {return (<TabBarIOS><TabBarIOS.Item title="React Native" selected={true}><NavigatorIOS initialRoute={{ title: 'React Native' }} /></TabBarIOS.Item></TabBarIOS>);} }??這里和vitual dom框架類似的地方都是都使用衍生的html描述語法來表示view層,而不同的是mnv模式是調用的nativeView來實現的衍生html的view展示。其實這里和上節中的實現唯一不同的地方是這里的view是native view。當然這只是一種實現,目前mnv的實現方案已經不止一種了,有人已經實踐了通過mvvm的編程方式來將viewModel渲染轉化為native view的方案。
七、總結
??總結下來,前端框架一次次進化,先從效率的方向上提升,然后再性能上完善,這里只是想提出mnv*的一個概念來描述前端native開發的這個階段。目前mnv的開發模式開始進入視線,也在快速地形成和建立生態。但盡管如此,我們如果需要選擇的技術棧方案,當然還是以最適合我們的作為最高原則。切忌過度設計。
轉載于:https://my.oschina.net/zhangstephen/blog/700822
總結
以上是生活随笔為你收集整理的mnv*框架开发时代的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: Spring WebFlux快速上手——
 - 下一篇: 2021-7-20