轉載自:https://segmentfault.com/a/1190000006435886
解決辦法:添加package.config.js配置文件中,添加本文章的紅色部分代碼
import vue
順利安裝完成並按 changelog 做了修改之后,啟動項目也正常,當我興致勃勃地打開 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 項目里就能跑,什么鬼啊!
但是,我並不妥協,分別打斷點運行,發現兩者竟然跑的不是同一段代碼,納尼!
import vue from 'vue'
同樣的 import
語句,卻有不一樣的結果,vue-router 中引的是 vue.js,而在我的項目中引的竟然是 vue.common.js...common...mon...n...
為什么會引 vue.common.js,from 'vue'
不該引的是 vue.js 么?這就要引入另一個知識點:package.json。
package.json 中的 main
屬性決定了,當項目被引入時,輸出的是哪個文件,而 vue 的 package.json 中的 main
指向的是 dist/vue.common.js
。
福利時間:推薦一個網站 json.is,它對 package.json 里的每條屬性都有詳細的解釋。
找到了問題產生的原因,那么解決也就輕而易舉了。
import vue from 'vue/dist/vue.js'
每次引用 vue 的時候都要寫那么長,一點都不優雅,而且為什么 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'
了。
你是不是以為這樣就結束了?不,對待一個問題要刨根問底,不能不求甚解。
為什么 vue 默認導出的是 vue.common.js,它和 vue.js 的區別在哪里,又有什么關系?
這個問題在囧克斯的博客中有提到。
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 是最恰當的。
路由升級
vue-router 的升級並不困難,參照 Releases Note 上的注釋修改應該沒有什么大問題,主要的變化有兩點:
-
路由配置從一系列的方法調用,變成了傳遞一個配置對象
-
原先的
v-link
指令,變成了router-link
Component,路徑指向用to
屬性
正當你以為會一路順風順水,輕松升級路由完成的時候,現實總會給你當頭一棒。
之前博客的 vue-router 中使用了 beforeEach
和 afterEach
方法,根據 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?
難道我哪里寫錯了?又經過我一番谷哥和查閱文檔之后,發現在下一個版本的 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.
好吧,它又被恢復回路由實例的方法了。那么,改回去
const router = new VueRouter(ROUTER_SETTING); router .beforeEach(() => { /* some function */ }) .afterEach(() => { /* some function */ });
OK,這樣總好了吧。然而,並沒有...console 中報出無法從 undefined
中讀取 afterEach
,好吧,我猜這應該是 beforeEach
中沒有像之前一樣返回路由對象,所以不能鏈式調用。
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 */ });
不過,不能鏈式調用似乎沒之前的優雅了哪~
最后,提一下 vue-router 2.0 里所有的 hook(就像之前的 beforeEach
, afterEach
,以及每個路由狀態中的 beforeEnter
, beforeRouteLeave
等)都具有相同的參數簽名,這在 Release Note 中也有提到。
fn (toRoute, redirect, next) {
// toRoute: {Object} 當前路由對象 // redirect: {Function} 調用跳轉至另一路由 // next: {Function} 調用繼續當前路由跳轉 // 什么都不做,則取消當前跳轉 }
路由升級完成后,如果控制台沒有什么報錯,那么,路由可以相互切換了,那些不依賴數據讀取的組件已經可以正常顯示了。
那些依賴數據讀取的組件哪?
這就要提到組件的生命周期鈎子(即 lifecycle hooks)。
Lifecycle hooks
生命周期鈎子應該算 vue 這次升級中 broken changes 最多的一部分了,對照 1.0 的文檔和 release note,作了下面這張表
vue 1.0+ | vue 2.0 | Description |
---|---|---|
init | beforeCreate | 組件實例剛被創建,組件屬性計算之前,如 data 屬性等 |
created | created | 組件實例創建完成,屬性已綁定,但 DOM 還未生成,$el 屬性還不存在 |
beforeCompile | beforeMount | 模板編譯/掛載之前 |
compiled | mounted | 模板編譯/掛載之后 |
ready | mounted | 模板編譯/掛載之后(不保證組件已在 document 中) |
- | beforeUpdate | 組件更新之前 |
- | updated | 組件更新之后 |
- | activated | for keep-alive ,組件被激活時調用 |
- | deactivated | for keep-alive ,組件被移除時調用 |
attached | - | 不用了還說啥哪... |
detached | - | 那就不說了吧... |
beforeDestory | beforeDestory | 組件銷毀前調用 |
destoryed | destoryed | 組件銷毀后調用 |
知道了 hooks 升級前后的對應關系,那么升級起來就輕而易舉了,改改組件的屬性名就可以了。
那么,改完屬性名是不是就完成了?然而並沒有。
因為,在 vue 1.0+ 中,如果一個組件和路由相關,那么,它就可能不單單有自己組件的 lifecycle hooks,它還會有基於 vue-router 的 lifecycle hooks。
而在 vue 2.0 中,router lifecycle hooks 全部被移除了,因為,這些 hooks 可以通過其他的方式來代替,這樣不但簡化了配置,還不用在組件中去處理路由相關的業務,降低了耦合。那這些 hooks 該如何替換,我們接下來就來看一下。
-
activate
&deactivate
:使用組件自身的 lifecycle hook 替代 -
data
:通過組件watch
屬性來監聽當前路由$route
的變化 -
canActivate
:由路由屬性beforeEnter
來代替 -
canDeactivate
:由路由屬性beforeRouteLeave
來代替 -
canReuse
:去除
那個這個是不是也直接改改屬性名就好了哪?
恩,差不多。不過需要注意的是,如果原先 hooks 中使用了有關路由信息的 transition
參數是肯定不能用了。比如,根據路由參數來進行查詢,原先通過 transition.to.params
獲取路由參數,現在就要通過剛剛提到的當前路由對象 this.$route.params
來獲取。
在升級這里的過程中,還遇到一個問題:當用戶輸入的 URL 滿足路由匹配,但根據路由參數無法獲得正確的文章時,我想讓路由直接跳轉到首頁。
在 1.0 版本中,我通過 transition.redirect('/');
就輕松的回到了首頁,由於 2.0 中沒有 transition
參數,而 $route
只包含當前路由的信息,並不包換路由切換的操作。那該怎么做哪?再一次谷哥和查閱文檔,然而一無所獲。
最后在 vue-router 的例子中找到了解決問題的鑰匙——$router
。
$router
返回的是整個項目路由的實例,它是只讀的。於是,剛剛那個問題就可以通過 this.$router.replace('/');
來解決。
這里還有一點,在 1.0 版本中組件配置 route 屬性時還可以設置一個叫 waitForData
的屬性。這個在 2.0 中,我還沒有找到直接的替換方式,不過,我在整個組件上添加 v-if
來處理。從理論和效果的角度上講,v-if
是可以替代原先的 waitForData
屬性,就似乎不那么優雅。