Troubleshooting of upgrading Vue from 1.0 to 2.0
系列文章:
- Vue 2.0 升(cai)級(keng)之旅 (本文)
- Vuex — The core of Vue application
本文不包含 Vue 2.0 所有新特性悉尾,如 SSR 等,本文并沒有涉及挫酿,本文只包含個人博客項目升級中所遇到的經(jīng)驗分享构眯,如有興趣,可以查看 Vue 2.0 changes log早龟。
前言
這節(jié)凈是些嘮叨鸵赖,只想看升(tian)級(keng)的可直接跳過。
從去年年底開始寫博客拄衰,那時對怎么搞個博客網(wǎng)站一竅不通,看別人用 Github Pages 寫博客挺贊的饵骨,就也想搞個玩玩翘悉。技術(shù)選型時,在 jekyll 和 hexo 中選擇了前者居触,或許你會問為什么妖混?估計當(dāng)時大腦的供氧量不足了吧...
于是,我的博客就這么誕生了轮洋。(jekyll 版的博客已經(jīng)廢棄了制市,如果你有興趣领追,可以查看之前的提交)
可是雌澄,用久了就發(fā)現(xiàn)并不怎么好用黑忱,雖然支持 markdown,可代碼塊要轉(zhuǎn)換成 highlighter 標(biāo)簽肢预;其次,主題模板是挺好看悉患,可換成中文字雜就那么別扭哪肩钠;還有,對 jekyll 的模板又不熟兽间,自定義也不方便历葛。
年初有一天,突然想到自己也是搞技術(shù)的嘀略,為啥不自己搭一個博客網(wǎng)站哪恤溶?對,順帶還能學(xué)學(xué)新技術(shù)帜羊,何樂而不為咒程。又到了技術(shù)選型的時候了,這次擺在我面前又有 2 個選擇逮壁,React 和 Vue孵坚,這次我選擇了后者。
Why窥淆?因為卖宠,后者更輕量級,也更貼近我熟悉的 Angular 的語法忧饭,還有扛伍,那時網(wǎng)上就有說今年 4 月 Vue 會升級到 2.0 和 Vue 兼具 React 和 Angular 的優(yōu)點等等。(好吧词裤,老實說刺洒,不選 React 只是因為不喜歡 JSX 而已。-_-||)
So吼砂,我就用 Vue 1.10+ 搭建了自己的新博客——Disciple.Ding Blog(點這里看源碼)逆航,并漸漸地往里添加一些新學(xué)到的東西,ES6, webpack, docker 等渔肩,并在 DAOcloud 上發(fā)布了因俐。(免費用了人家那么久的服務(wù),在這里做個硬廣也是應(yīng)該的周偎,DAOcloud 的確很好用抹剩,特別和 Github 綁定之后能自動構(gòu)建,應(yīng)用更新也及其簡單蓉坎,只是有個缺點就是有帶寬限制澳眷。)
在不久之前,Vue 如約發(fā)布了 2.0 版本蛉艾。正如計劃之初钳踊,博客 Vue 的版本也將升級到 2.0衷敌。
說了那么多,再不進入正題就要變成標(biāo)題黨了箍土。好逢享,那就開始我們的升(cai)級(keng)之旅。
升(tian)級(keng)之旅
首先吴藻,升級依賴瞒爬。
npm install vue@next vue-router@next --save
import vue
順利安裝完成并按 changelog 做了修改之后,啟動項目也正常沟堡,當(dāng)我興致勃勃地打開 Browser侧但,駕輕就熟地輸入 localhost,并自然而然地按下 Enter航罗,一切水到渠成禀横。
然而,迎接我的竟是一片白板粥血,控制臺里赫然映著一串紅字柏锄。
[Vue warn]: You are using the runtime-only build of Vue where the template option is not available. Either pre-compile the templates into render functions, or use the compiler-included build. (found in root instance)
What? template 選項不能用了,changelog 沒提到案纯鳌趾娃?但 vue-router 的例子中都在用啊,什么鬼缔御?甚至我將代碼全部替換成例子中的代碼依舊無法運行抬闷,但在 vue-router 項目里就能跑,什么鬼案弧笤成!
但是,我并不妥協(xié)眷茁,分別打斷點運行炕泳,發(fā)現(xiàn)兩者竟然跑的不是同一段代碼,納尼上祈!
import vue from 'vue'
同樣的 import
語句喊崖,卻有不一樣的結(jié)果,vue-router 中引的是 vue.js雇逞,而在我的項目中引的竟然是 vue.common.js...common...mon...n...
為什么會引 vue.common.js,from 'vue'
不該引的是 vue.js 么茁裙?這就要引入另一個知識點:package.json塘砸。
package.json 中的 main
屬性決定了,當(dāng)項目被引入時晤锥,輸出的是哪個文件掉蔬,而 vue 的 package.json 中的 main
指向的是 dist/vue.common.js
廊宪。
福利時間:推薦一個網(wǎng)站 json.is,它對 package.json 里的每條屬性都有詳細的解釋女轿。
找到了問題產(chǎn)生的原因箭启,那么解決也就輕而易舉了。
import vue from 'vue/dist/vue.js'
每次引用 vue 的時候都要寫那么長蛉迹,一點都不優(yōu)雅傅寡,而且為什么 vue-router 的例子可以用啊北救?
我要一探究竟荐操。確認了 vue-router 中依賴的 vue 的 package.json 文件中的 main
字段指向的也是 dist/vue.common.js
。那就只有一個可能了珍策,webpack 對引入做了處理托启,查看 webpack.config.js
module.exports = {
// 省略...
resolve: {
alias: {
'vue': 'vue/dist/vue.js'
}
},
...
果然啊~他用 webpack 的別名功能把 vue/dist/vue.js
命名成了 vue,防不勝防攘宙。
在自己項目的 wepack.config.js 里同樣給 vue 起別名屯耸,這樣就又能愉快地使用 import vue from 'vue'
了。
你是不是以為這樣就結(jié)束了蹭劈?不疗绣,對待一個問題要刨根問底,不能不求甚解链方。
為什么 vue 默認導(dǎo)出的是 vue.common.js持痰,它和 vue.js 的區(qū)別在哪里,又有什么關(guān)系祟蚀?
這個問題在囧克斯的博客中有提到工窍。
Vue 最早會打包生成三個文件,一個是 runtime only 的文件 vue.common.js前酿,一個是 compiler only 的文件 compiler.js患雏,一個是 runtime + compiler 的文件 vue.js。
也就是說罢维,vue.js = vue.common.js + compiler.js
淹仑,而如果要使用 template
這個屬性的話就一定要用 compiler.js,那么肺孵,引入 vue.js 是最恰當(dāng)?shù)摹?/p>
第一句代碼就把我坑那么久匀借,后面還會有多少坑哪?
路由升級
vue-router 的升級并不困難平窘,參照 Releases Note 上的注釋修改應(yīng)該沒有什么大問題吓肋,主要的變化有兩點:
- 路由配置從一系列的方法調(diào)用,變成了傳遞一個配置對象
- 原先的
v-link
指令瑰艘,變成了router-link
Component是鬼,路徑指向用to
屬性
正當(dāng)你以為會一路順風(fēng)順?biāo)粑瑁p松升級路由完成的時候,現(xiàn)實總會給你當(dāng)頭一棒均蜜。
之前博客的 vue-router 中使用了 beforeEach
和 afterEach
方法李剖,根據(jù) Release Note
- router.beforeEach (replaced by the beforeEach option)
- router.afterEach (replaced by the afterEach option)
行,那我把它改到配置里
const ROUTER_SETTING = {
routes: [
// 省略...
],
beforeEach: () => { /* some function */ },
afterEach: () => { /* some function */ }
}
But, not work. What's wrong?
難道我哪里寫錯了囤耳?又經(jīng)過我一番谷哥和查閱文檔之后篙顺,發(fā)現(xiàn)在下一個版本的 Release Note 中有這么一段
- beforeEach and afterEach are reverted as router instance methods (options removed). This makes it more convenient for plugins/modules to add hooks after the router instance has been created.
好吧,它又被恢復(fù)回路由實例的方法了紫皇。那么慰安,改回去
const router = new VueRouter(ROUTER_SETTING);
router
.beforeEach(() => { /* some function */ })
.afterEach(() => { /* some function */ });
OK,這樣總好了吧聪铺。然而化焕,并沒有...console 中報出無法從 undefined
中讀取 afterEach
,好吧铃剔,我猜這應(yīng)該是 beforeEach
中沒有像之前一樣返回路由對象撒桨,所以不能鏈式調(diào)用。
class VueRouter {
// 省略...
beforeEach (fn: Function) {
this.beforeHooks.push(fn)
}
afterEach (fn: Function) {
this.afterHooks.push(fn)
}
// 省略...
}
看一眼源碼键兜,果然如此凤类。
那再將之前的代碼稍作修改就可以了。
const router = new VueRouter(ROUTER_SETTING);
router.beforeEach(() => { /* some function */ });
router.afterEach(() => { /* some function */ });
不過普气,不能鏈式調(diào)用似乎沒之前的優(yōu)雅了哪~
最后谜疤,提一下 vue-router 2.0 里所有的 hook(就像之前的 beforeEach
, afterEach
,以及每個路由狀態(tài)中的 beforeEnter
, beforeRouteLeave
等)都具有相同的參數(shù)簽名现诀,這在 Release Note 中也有提到夷磕。
fn (toRoute, redirect, next) {
// toRoute: {Object} 當(dāng)前路由對象
// redirect: {Function} 調(diào)用跳轉(zhuǎn)至另一路由
// next: {Function} 調(diào)用繼續(xù)當(dāng)前路由跳轉(zhuǎn)
// 什么都不做,則取消當(dāng)前跳轉(zhuǎn)
}
路由升級完成后仔沿,如果控制臺沒有什么報錯坐桩,那么,路由可以相互切換了封锉,那些不依賴數(shù)據(jù)讀取的組件已經(jīng)可以正常顯示了绵跷。
那些依賴數(shù)據(jù)讀取的組件哪?
這就要提到組件的生命周期鉤子(即 lifecycle hooks)成福。
Lifecycle hooks
生命周期鉤子應(yīng)該算 vue 這次升級中 broken changes 最多的一部分了碾局,對照 1.0 的文檔和 release note,作了下面這張表
vue 1.0+ | vue 2.0 | Description |
---|---|---|
init | beforeCreate | 組件實例剛被創(chuàng)建奴艾,組件屬性計算之前擦俐,如 data 屬性等 |
created | created | 組件實例創(chuàng)建完成,屬性已綁定握侧,但 DOM 還未生成蚯瞧,$el 屬性還不存在 |
beforeCompile | beforeMount | 模板編譯/掛載之前 |
compiled | mounted | 模板編譯/掛載之后 |
ready | mounted | 模板編譯/掛載之后(不保證組件已在 document 中) |
- | beforeUpdate | 組件更新之前 |
- | updated | 組件更新之后 |
- | activated | for keep-alive ,組件被激活時調(diào)用 |
- | deactivated | for keep-alive 品擎,組件被移除時調(diào)用 |
attached | - | 不用了還說啥哪... |
detached | - | 那就不說了吧... |
beforeDestory | beforeDestory | 組件銷毀前調(diào)用 |
destoryed | destoryed | 組件銷毀后調(diào)用 |
知道了 hooks 升級前后的對應(yīng)關(guān)系埋合,那么升級起來就輕而易舉了,改改組件的屬性名就可以了萄传。
那么甚颂,改完屬性名是不是就完成了?然而并沒有秀菱。
因為振诬,在 vue 1.0+ 中,如果一個組件和路由相關(guān)衍菱,那么赶么,它就可能不單單有自己組件的 lifecycle hooks,它還會有基于 vue-router 的 lifecycle hooks脊串。
而在 vue 2.0 中辫呻,router lifecycle hooks 全部被移除了,因為琼锋,這些 hooks 可以通過其他的方式來代替放闺,這樣不但簡化了配置,還不用在組件中去處理路由相關(guān)的業(yè)務(wù)缕坎,降低了耦合怖侦。那這些 hooks 該如何替換,我們接下來就來看一下谜叹。
-
activate
&deactivate
:使用組件自身的 lifecycle hook 替代 -
data
:通過組件watch
屬性來監(jiān)聽當(dāng)前路由$route
的變化 -
canActivate
:由路由屬性beforeEnter
來代替 -
canDeactivate
:由路由屬性beforeRouteLeave
來代替 -
canReuse
:去除
那個這個是不是也直接改改屬性名就好了哪匾寝?
恩,差不多叉谜。不過需要注意的是旗吁,如果原先 hooks 中使用了有關(guān)路由信息的 transition
參數(shù)是肯定不能用了。比如停局,根據(jù)路由參數(shù)來進行查詢很钓,原先通過 transition.to.params
獲取路由參數(shù),現(xiàn)在就要通過剛剛提到的當(dāng)前路由對象 this.$route.params
來獲取董栽。
在升級這里的過程中码倦,還遇到一個問題:當(dāng)用戶輸入的 URL 滿足路由匹配,但根據(jù)路由參數(shù)無法獲得正確的文章時锭碳,我想讓路由直接跳轉(zhuǎn)到首頁袁稽。
在 1.0 版本中,我通過 transition.redirect('/');
就輕松的回到了首頁擒抛,由于 2.0 中沒有 transition
參數(shù)推汽,而 $route
只包含當(dāng)前路由的信息补疑,并不包換路由切換的操作。那該怎么做哪歹撒?再一次谷哥和查閱文檔莲组,然而一無所獲。
最后在 vue-router 的例子中找到了解決問題的鑰匙——$router
暖夭。
$router
返回的是整個項目路由的實例锹杈,它是只讀的。于是迈着,剛剛那個問題就可以通過 this.$router.replace('/');
來解決竭望。
這里還有一點,在 1.0 版本中組件配置 route 屬性時還可以設(shè)置一個叫 waitForData
的屬性裕菠。這個在 2.0 中咬清,我還沒有找到直接的替換方式,不過糕韧,我在整個組件上添加 v-if
來處理枫振。從理論和效果的角度上講,v-if
是可以替代原先的 waitForData
屬性萤彩,就似乎不那么優(yōu)雅粪滤。
剩余其他小點,看控制臺報錯信息雀扶,然后查查 Release Note 都能輕松處理啦~
寫在最后
如果現(xiàn)在再讓我選一個技術(shù)來搭博客的話予权,我會選 React。為啥浪册?
因為 vue 我已經(jīng)玩過啦扫腺,哈哈哈~
最后,借用外國網(wǎng)友的一句話:
I'm constantly rewriting / refactoring this silly little blog using the latest and buzziest tech, so that I can stay up to date on these libraries and frameworks.
這也是我自己搭博客村象,而不是直接使用博客系統(tǒng)的主要原因笆环。
最后的最后,安利下自己的 Blog厚者,以及 Source Code躁劣。
歡迎交流,噴子繞道库菲。