博主也是vuejs萌新,公司僅我一個前端,收到作h5的需求後,立刻想到要用下vuejs,因而說服領導,開始慢慢鑽研,如今記錄一下踩到的坑。這些坑主要是在一些組件的使用上,其它的只要好好看官方文檔就行了,vue,vue-router, vuex的文檔至關重要。javascript
歡迎體驗提bug 墨瞳漫畫 m.cm233.comcss
router這裏踩的坑主要是組件的重用。構建單頁面大型應用的話,確定要開啓組件的緩存的,由於通常會要求後退的時候不要從新加載頁面,並且要記住原始的滾動位置。
首先,引入router-view的地方要加上keep-alivehtml
<router-view keep-alive></router-view>
而後開啓html5 history模式,並開啓位置紀錄前端
const router = new Router({ history: true, // use history=false when testing saveScrollPosition: true })
開啓keep-alive之後,當要求一個組件的內容發生變化時,好比 漫畫詳情頁面是一個路由帶參數的組件,當參數變化時,router會重用這個組件,而不是從新請求數據,這顯然是不符合要求的,因此正確的姿式是:
首先,用一個字段保存這個路由參數,
用router的鉤子函數data獲取路由變化參數,保存到字段裏vue
route:{ data: function(transition){ this.bookId = transition.to.params.id; } }
寫一個watcher來拉取數據並填充模版,由於在data鉤子函數中,咱們已經修改了相應字段,因此當路由參數更改時會直接觸發這個watcherhtml5
watch: { 'bookId' : function(val){ //do something } }
若是是多個參數的,能夠把這些參數放到一個對象裏,watcher採用深監測java
route:{ data: function(transition){ this.watcher.type = transition.to.params.type; this.watcher.id = transition.to.query.id; } }, watch : { 'watcher' : { handler: function(val){ //do something window.scrollTo(0,0);// 不使用緩存時,不使用記錄好的用戶位置,滑倒頂部 }, deep: true } }
一開始沒有用這種方法出了不少的bug,改了之後,路由和緩存方面的邏輯瞬間就變得清晰了,組件的切換也更加流暢了。
第二個坑就是關於緩存頁面瀏覽位置的紀錄,router是經過html5 history的pushState來紀錄當前滾動位置的,切換路由的時候,把當前位置push進去,用戶後退時,會觸發onpopstate事件,這個時候再把位置取出來並滾動到指定位置,可是!某些瀏覽器自己也設置了一些奇怪的位置滾動,vue-router的滾動就失效了,因此須要延遲執行一下webpack
window.addEventListener("popstate",function(e){ setTimeout(()=>{ window.scrollTo(0,e.state.pos.y);//經過打log,發現了位置紀錄在這個變量裏了 },300) },false);
然而,瀏覽器只能記錄一個位置,因此會有這樣的狀況: 從m.cm233.com 到 m.cm233.com/book,再返回到m.cm233.com,這時瀏覽器跳到了當時記錄的位置,可是再前進到/book時,瀏覽器仍是會停在首頁的那個位置上,這個bug暫時尚未解決,好在用戶場景不是不少。因此告訴咱們,子頁面路由參數變化的時候,要把滾動條人工弄到最上面,要否則就會滾動到入口頁面的瀏覽位置。也就是watcher裏還要加一句如上的滾動。ios
頁面標題也是要手動更改的,因此每一個頁面要放一個專門的title變量存一下,而後在data鉤子函數(用於組件緩存時) 和 路由參數的watcher(用於組件更新時) 裏 都改變titlegit
route:{ data: function(transition){ this.title = 'hiahia'; document.title = this.title; } }, watch : { 'id' : function(val){ this.title = 'hiahia'; document.title = this.title; } }
一般頁面的標題不是固定的,用變量存儲title,主要是爲了記住上一次組件被用的時候的title,以便於重用的時候更換。
然而,ios微信不會監測document.title的變化,因此要寫一個專門針對它的hack,經過建立iframe
//全局函數 window.isWeiXin = function(){ var ua = window.navigator.userAgent.toLowerCase(); if(ua.match(/MicroMessenger/i) == 'micromessenger'){ return true; }else{ return false; } } window.weiXinChange = function(title){ if(window.isWeiXin()){ document.title = title; var iframe = document.createElement('iframe'); iframe.src = './favicon.ico'; iframe.style.display = 'none'; iframe.onload = function(){ setTimeout(function() { document.body.removeChild(iframe); }, 0); } document.body.appendChild(iframe); } } //組件中 route:{ data: function(transition){ this.title = '墨瞳漫畫'; document.title = this.title; window.weiXinChange(this.title); } }, watch : { 'id' : function(val){ this.title = '墨瞳漫畫'; document.title = this.title; window.weiXinChange(this.title); } }
(爲何不本身寫!)
組件地址 https://github.com/ElemeFE/vue-infinite-scroll 餓了麼出品
使用方法
main.js
import Scroll from 'vue-infinite-scroll' Vue.use(Scroll)
組件中
<dl v-infinite-scroll="loadMore()" infinite-scroll-disabled="busy" infinite-scroll-distance="7"> <template v-for="item in list"> <dd class="page-item"> </dd> </template> </dl>
其中busy這個變量比較重要,他控制着這個指令是否繼續執行,當沒有下一頁數據的時候,應該把busy置爲true來關閉滾動加載。正在讀取下一頁數據時,要先把busy置爲true,數據返回時在置爲false
loadmore(){ this.busy = true; someApi.someFunction().then((data) => {this.busy = false;}) }
可是這個組件在路由切換的時候會出問題,routerView被移除時,組件會觸發加載(大概是由於頁面高度忽然塌陷),並且會一直加載到咱們本身設置的中止條件(busy=true)。因此離開頁面的時候,須要在路由的deactivate鉤子函數裏把滾動關掉,再次進入頁面的時候再開啓(路由無變化在data鉤子函數裏開啓,有變化的話在watcher裏開啓,若是不須要在路由改變時向子組件延時傳遞參數也能夠都在data鉤子函數裏開啓)
route:{ deactivate: function(transition){ this.busy = true; transition.next(); }, data: function(transition){ if(){ this.busy = false; }//這裏輸入組件路由參數沒有變化的條件 } }
(爲何不本身寫!)
網上找了幾個lazyload的組件,都不太好使,就本身改了一個,是改了一個,原組件叫vue-lazyload, 毛病還挺多的,寫這個組件的人估計沒有真正在大項目中用過就匆匆發佈在npm了,es6版本也寫的不三不四的 - -,不過仍是很厲害,本身寫的話毛病確定會更多。我改後的放在https://github.com/Ganother/blog/blob/master/lazyload.js了,是個較爲穩定的版本。其中過渡動畫寫在img-loaded這個class裏
/*簡單的透明度漸入,圖片加載完成後會刪掉這個class,以防router切換緩存頁面時再次引發動畫*/ .img-loaded { animation: loaded .2s ease-in-out; } @keyframes loaded { 0%{ opacity: 0; } 100%{ opacity: 1; } }
let loadingJpg = require('assets/loading.jpg');//這裏引入一張loading圖,會被轉成base64 Vue.use(VueLazyload, { preLoad: 1.3, //圖片頂端距窗口頂端1.3個屏幕高度時開始加載 loading: loadingJpg, error: loadingJpg })
自適應的圖片:若是服務端傳過來的圖片帶了寬高信息,能夠在img外層包一個class爲img-bar的元素,圖片過來的時候先設置一個min-height爲響應高,組件在圖片加載後會自動取掉這個min-height。這樣能夠防止loading圖和圖片大小不同引發的頁面跳動繼而致使的加載圖片時機錯誤。
跨域時,會先發送一個空的options請求來查看接口是否是支持跨域,再發送一次真實請求。還不是很瞭解這種方式的好處,當接口較多時,請求數量多了一倍也是有點尷尬的,因此要設置一下。並且若是接口每次都打印空參數的log的話。。。嗯。
Vue.http.options.emulateJSON = true; Vue.http.options.headers={ 'Content-Type':'application/x-www-form-urlencoded; charset=UTF-8' };
vue-cli直接構建的,src裏的目錄以下
api 放一些ajax請求接口的函數
assets 放一些靜態資源,圖片,公共sass
directives 放一些指令js,好比改動後的lazyload
pages 頁面入口組件,用在router中
components 小組件們
vuex vuex
app.vue
main.js
另外,能夠修改下生成的靜態文件,vue-cli默認聲稱的靜態文件時間戳是加在文件名上的,如app.fefdfd7s8f78sd7.js,這樣版本迭代很快後會使服務器上積壓過多無用文件,咱們但願這樣加時間戳 app.js?t=32j32ih4u32h 因此改一下webpack.prod.conf.js就行了,以下
output: { path: config.build.assetsRoot, filename: utils.assetsPath('js/[name].js?t=[chunkhash]'), chunkFilename: utils.assetsPath('js/[id].js') } new ExtractTextPlugin(utils.assetsPath('css/[name].css?t=[contenthash]')),