1. 程式人生 > >無痛學會各種 2 的 Vue2+Vuex2+Webpack2 前後端同構渲染

無痛學會各種 2 的 Vue2+Vuex2+Webpack2 前後端同構渲染

感謝有人看我扯技術,這篇文章主要介紹最近非常火的vue2前端框架的特點和vue2+vuex2+webpack2各種2的前後端同構渲染架構搭建流程,最後會附上程式碼,文章想到啥寫啥,如果存在錯誤,或者大家有什麼意見建議,歡迎大家指出來

Vue2 和 Vue1 的對比

vue2出來之後,基本上逛論壇逛技術群都能看到各種文章,各種討論 ,一時間大家都在學習vue2了 ,我今年年初就開始接觸vue,最初也是在react ,angular,vue 之中對比選擇,最終選擇了vue,因為其對前端比較友好(使用正常的模板,而不是JSX)、概念及學習成本相對簡單(對於團隊開發,引入技術必須要考慮其學習成本),下面介紹下我理解的vue2和vue1的不同之處 ,如有不足,歡迎補充:

1.引入了virtual Dom

在vue1中,資料和檢視的繫結流程是通過 Object.defineProperty將資料轉化為getter/setter,getter/setter中加入watcher,當對資料進行操作的時候,setterwatch被觸發重新計算,然後更新和這個資料有關聯的dom元素,這就是vue1的資料驅動檢視原理
在vue2中,資料的繫結和觸發和vue1相同,基本原理都是通過Object.defineProperty對資料加入'鉤子',以便在資料發生變化的時候得以響應,而在響應之後,不像vue1一樣直接更新dom元素,而是放入virtual Dom中,進行比對計算,然後對dom元素做相應的處理。下面是vue1和vue2的響應流程對比

vue:

vue2:

關於virtual Dom: 虛擬dom最初是在react上面認識的,其實做的事情就是在js記憶體中建立好dom的結構,然後再更新虛擬dom時做差異比對,將差異的地方真正更新到頁面中,做到最小化頁面的渲染。當然,也不是說對於所有情景,虛擬dom的效能都是最好的,畢竟比起直接操作dom元素,他還是需要在記憶體中進行計算,因此對於少量的元素更新,可能其效能比起直接操作dom元素要差。當然虛擬dom的引入,不只是在效能方面的考慮,虛擬dom可以帶來程式設計的變化,比如你可以使用render方法直接建立新的節點,虛擬dom也是vue2可以進行服務端渲染的關鍵,由於虛擬dom是在記憶體重點,vue2的ssr可以將虛擬dom直接生成html的字串,從而實現ssr。除此之外,vue2 從模板到 virtuel-DOM 的編譯階段使用了一些高階優化:

(1). 它會檢測出靜態的 class 名和 attributes 這樣它們在初始化渲染之後就永遠都不會再被比對。 
(2). 它會檢測出最大靜態子樹 (就是不需要動態性的子樹) 並且從渲染函式中萃取出來。這樣在每次重渲染的時候,它就會直接重用完全相同的 virtual nodes 同時跳過比對。
這些高階優化通常只會在使用 JSX 時通過 Babel plugin 來做,但是 vue2 即使在使用瀏覽器內的編譯器時也能做到。

2.元件事件傳遞機制的改變,元件資料雙向繫結的去除

vue2元件廢除了$dispath/$broadcast父子元件的事件傳播方式,廢除了過濾器,props引數等的資料雙向繫結以及處理功能,說明作者希望使用者通過建立全域性的狀態管理,事件管理機制,通過使用事件中心,允許元件自由交流,無論元件處於元件樹的哪一層,將狀態管理集中在一起處理,官方提供的vuex就是用來幾種管理狀態的。

3.服務端渲染 ssr:server-side-render

由於virtual dom的引入,使得vue的服務端渲染成為了可能,下面是官方 vue-server-renderer提供的渲染流程圖:

可以看出vue的server-side-render有三部分組成,一部分是頁面的原始碼(source),還有node層的渲染部分和瀏覽器端的渲染部分。

source分為兩種entry point,一個是前端頁面的入口client entry,主要是例項化Vue物件,將其掛載到頁面中;另外一個是後端渲染服務入口server entry,主要是控服務端渲染模組回撥,返回一個Promise物件,最終返回一個Vue物件(經過測試,直接返回Vue物件也是可以的);

前面的source部分就是業務開發的程式碼,開發完成之後通過 webpack 進行構建,生成對應的bundle,這裡不再贅述client bundle,就是一個可在瀏覽器端執行的打包檔案;這裡說下server bundle, vue2提供 vue-server-renderer模組,模組可以提供兩種render: rendererer/bundleRenderer ,下面分別介紹下這兩種render。

renderer接收一個vue物件 ,然後進行渲染,這種對於簡單的vue物件,可以這麼去做,但是對於複雜的專案,如果使用這種直接require一個vue物件,這個對於服務端程式碼的結構和邏輯都不太友好,首先模組的狀態會一直延續在每個請求渲染請求,我們需要去管理和避免這次渲染請求的狀態影響到後面的請求,因此vue-server-renderer提供了另外一種渲染模式,通過一個 bundleRenderer去做渲染。

bundleRenderer是較為複雜專案進行服務端渲染官方推薦的方式,通過webpack以server entry按照一定的要求打包生成一個 server-bundle,它相當於一個可以給服務端用的app的打包壓縮檔案,每一次呼叫都會重新初始化 vue物件,保證了每次請求都是獨立的,對於開發者來說,只需要專注於當前業務就可以,不用為服務端渲染開發更多的邏輯程式碼。
renderer生成完成之後,都存在兩個介面,分別是renderToStringrenderToStream,一個是一次性將頁面渲染成字串檔案,另外一個是流式渲染,適用於支援流的web伺服器,可以是請求服務的速度更快

4.除了上面說的那些不同,vue2在生命週期管理,動畫機制等地方都與vue有些差別,具體請瀏覽migration

Vuex2 和 Vuex 、Webpack2 和 Webpack 的不同之處

vuex

相對於 vue2和vue 較大改動,vue的狀態管理工具 vuex的改動不是很大,底層改動由於時間關係還沒有來得及細究,但是在使用方面多了幾個 Helper,利用ES6的展開函式可以更加方便的使用state,getters,mutations,actions。下面簡單介紹下vuex各個部分的概念

  • state是一個全域性的狀態儲存,資料會儲存在其中,vue元件可以直接訪問其中的值,但是隻可以讀,不可以進行寫操作

  • getter,有些時候我們需要對獲取的資料進行加工,而不是直接獲取state中的資料,這時候可以通過getter定義函式,返回對應的資料

  • mutations是vuex中唯一一個可以修改資料的地方,mutations可以定義事件函式,在vue元件中可以通過commit發射事件,呼叫函式。需要注意的是,mutations中的操作必須是同步的,不可以存在非同步操作的情況。

  • actions和 mutation比較相似,不同的是actions中不直接修改state,而是通過commit呼叫mutations修改資料,而且actions中可以存在非同步處理邏輯

webpack

webpack2 和 webpack對比,有以下的新特性:

  1. ES6 Modules : webpack 2 已經支援原生的 ES6 的模組載入器了,這意味著 webpack 2 能夠理解和處理 import和export了,而不用把他們轉化成 CommonJS 來處理了。

  2. 用 ES6 來做程式碼拆分 : ES6 的模組載入器定義了System.import這一個方法,System.import能夠在執行時動態載入 ES6 模組。

  3. 混合使用 ES6 和 AMD 和 CommonJS (Mixing ES6 with AMD and CommonJS)

更加具體的新特性可以瀏覽連結地址

從零開始搭專案

好了,前面扯了那麼多東西,估計沒什麼人看,我們還是迴歸題目,開始敲程式碼吧,哈哈,接下來我會使用vue2 + vuex2 + webpack2 搭建一個簡單的 ssr專案,能夠直出頁面,還能夠儲存成靜態檔案。雖然官方頁面響應的例項vue-hackernews-2.0,但是如果一開始把程式碼下下來,還是不太容易理解的,所以我參考其例子,從零開始搭建專案,原始碼在文章的最後

首先當然是使用npm init新建一個專案
然後往package.json中寫入下列依賴

然後執行 npm i ,然後去上個廁所,喝杯茶也行,等所有的依賴安裝完畢
介紹下一些模組的作用 vue,vuex 為vue專案使用的基本框架,express,vue-server-renderer,serialize-javascript為服務端渲染使用的模組,babel-*為ES6轉換成ES5模組,其他的webpack*,*-loader為webpack構建所需要的模組,如果需要專案學習webpack的使用,可以閱讀官方文件

安裝階段完成了,下面進入愉快的編碼階段了,其實按照流程應該是編碼同時搞定打包、開發環境配置等工作的,為了文章的效果,就分開說了

製作的頁面是一個沒有什麼業務情景的頁面(請原諒我,想到什麼就寫什麼程式碼了),主要是為了演示元件的引用流程,vuex狀態管理以及引用,狀態改變之後的檢視更新,非同步操作的檢視更新,所以,當你下了原始碼,大概頁面,你會看到下面這個奇奇怪怪的東西:

好了不要在意這些細節了,我們來看看這個怪怪的東西是怎麼出來的,先展示下專案最終的目錄結構:

其中index.html就是頁面最終生成頁的模板頁,裡面有簡單的頭部資訊和佔位符,可以在服務端渲染後進行內容替換
app.js就是頁面的邏輯入口檔案,Vue物件在這裡例項化,其中使用的store,route可以在例項化中傳入

可以看到app.js引用了App.vue元件,.vue是vue官方推出的單檔案開發方式,配合webpack的vue-loader可以方便的實現模組化開發,.vue檔案在打包的時候會被編譯成為一個js物件,裡面包含一個render方法,用於渲染頁面,下面是App.vue檔案

可以看到頁面包含三個部分template 、script 、style,其中template為元件使用的模板,現在vue2除了使用template,還可以使用JSX和js模板,但是相對其他兩種,template對於前端開發者來說還是比較直觀的,script為元件的邏輯部分,使用es6的進行模組化,構建的時候會使用Vue.extend生成一個元件,style為頁面的樣式部分,可以指定lang來宣告使用的樣式語法型別,可以用原生的css,也可以用stylus,sass等等,只要配置不同的webpack loader進行進行編譯就行了,另外可以指定 scoped,使得元件中的樣式只對元件生效,不會影響其他元件,不用擔心命名重複的問題,其原理是在生成的時候為標籤生成一段隨機數(沒研究過生成數的演算法),並且為選擇器加上對應隨機數的屬性選擇器。

可以看到元件import另外一個List.vue元件,並且在components中進行了引用,template中進行了引用,這就實現了元件的巢狀和複用,下面是List.vue檔案

這個檔案使用了vuex管理的資料,在此前的版本的vuex中,在元件使用資料需要寫很多的computed,methods,在新版本的可以配合ES6的展開函式和vuex的helper,簡寫很多函式,元件部分內容就說到這裡了,可能有人會說啥是computed,啥是methods,這些通通自己看文件
總的來說,寫一個元件需要了解下面幾點:

  1. 模板指令,例如v-for,v-bind,v-on

  2. 資料使用配置屬性data,computed,props,watch

  3. 元件的生命週期屬性created,mounted

  4. 全域性方法Vue.set,Vue.nextTick

  5. 進階開發: 動畫效果,自定義指令,自定義外掛,混合組件開發

下面說下用vuex做狀態管理,下面是store/index.js檔案

可以看到使用vuex需要在Vue.use中引入,然後例項化一個Vuex.Store物件就可以了,物件中需要定義state,actions,mutations,getters等內容,這樣子就可以建立一個全域性的狀態管理機制,可以從應用的頂端去處理資料,各個元件中對資料進行操作也是通過事件直接傳遞到Vuex中進行資料更新,然後再進行響應到其他使用同個資料的元件中,進行檢視更新。

專案的邏輯程式碼已經完成了,但是對照上面ssr的概念,會發現還少了兩個webpack的entry point,一個是前端程式碼的入口,可以是供服務端渲染的入口,下面是前端client-entry.js檔案

檔案引入了app.js,判斷如果在服務端渲染時已經寫入狀態,則將vuex的狀態進行替換,使得服務端渲染的html和vuex管理的資料是同步的。然後將vue例項掛載到html指定的節點中。

下面是server-entry.js檔案

這裡面服務端會傳過來一個context物件,可以從獲取資訊,也可以寫入資訊,可以看到將現有的vuex state賦值給context,給服務端渲染使用,最後返回vue物件(文件中寫著需要返回一個Promise物件,物件中再返回一個vue物件,經過實驗直接返回也是可以的,如果應用中存在非同步操作會影響檢視和vuex資料狀態,那麼應該返回一個Promise物件,使得服務端得到的vue物件是最後資料和檢視同步統一的)

程式碼擼完了,下面要讓他跑起來了,配置分為兩部分,一個是webpack打包的配置,一個是服務端渲染伺服器的搭建,這裡使用express進行伺服器的簡單搭建,不涉及任何負載均衡和效能優化問題,下面分別說說這兩個部分 :

  • webpack打包:webpack打包主要有三個檔案webpack.base.config.js,webpack.client.config.js,webpack.server.cofnig.js,其中base主要配置了對應檔案型別的loader,還有指定了entry的切割點,將業務程式碼和庫,client指定了client-entry.js作為entry point ,還將庫檔案和業務檔案進行分別打包,還有一些圖片處理,程式碼壓縮的工作。server指定了server-entry.js作為entry point,並且指定了打包了型別標準是commonjs2,供服務端渲染模組使用。

  • 而在開發過程中,可以使用webpack-hot-middleware/client,webpack-hot-middleware去實現程式碼的watch和重新構建雙端的程式碼的流程,是得開發更加便捷,具體配置在setup-dev-server.js

  • 最後說一下服務端渲染的伺服器配置,服務端部分使用vue-server-renderer模組的createBundleRenderer通過傳入剛才webpack生成的server-bundler去生成一個bundleRenderer,就可以呼叫renderToStream或者renderToString渲染頁面了,具體配置在server.js

具體怎麼跑起專案可以看下package.json的scripts屬性,其中dev是開發用,start是正式環境動態生成頁面用,build可以直接生成client-bundlerserver-bundler

基本上比較完整的vue2 前段端同構渲染已經介紹完了,下面說下我對框架的看法,前端框架這個東西基本上就是一時火一個,我們在學習新東西的同時也應該不忘老本,要有自己的技術棧和工作流,就像《人月神話》中的一句話說的好,沒有解決任何事情的銀彈,對於不同的專案,不同的業務情景,應該採取不同的框架,使用最合適的開發架構去開發。

附上程式碼 點我點我,給個star唄~