Vue 項目裏戳中你痛點的問題及解決辦法(更新)

最近要求使用vue進行先後端分離開發微信公衆號,不斷摸索踩坑以後,總結出以下幾點vue項目開發中常見的問題及解決辦法。若是你是vue大佬,請忽略小弟的愚見^V^javascript

  • 列表進入詳情頁的傳參問題。
  • 本地開發環境請求服務器接口跨域的問題
  • axios封裝和api接口的統一管理
  • UI庫的按需加載
  • 如何優雅的只在當前頁面中覆蓋ui庫中組件的樣式
  • 定時器問題
  • rem文件的導入問題
  • Vue-Awesome-Swiper基本能解決你全部的輪播需求
  • 打包後生成很大的.map文件的問題
  • fastClick的300ms延遲解決方案
  • 組件中寫選項的順序
  • 路由懶加載(也叫延遲加載)
  • 開啓gzip壓縮代碼
  • 詳情頁返回列表頁緩存數據和瀏覽位置、其餘頁面進入列表頁刷洗數據的實踐
  • css的scoped私有做用域和深度選擇器
  • hiper打開速度測試
  • vue數據的兩種獲取方式+骨架屏
  • 自定義組件(父子組件)的雙向數據綁定
  • 路由的拆分管理
  • mixins混入簡化常見操做
  • 打包以後文件、圖片、背景圖資源不存在或者路徑錯誤的問題
  • vue插件的開發、發佈到github、設置展現地址、發佈npm包

===========================這是華麗麗的分割線~~=========================php

列表進入詳情頁的傳參問題。

例如商品列表頁面前往商品詳情頁面,須要傳一個商品id;

<router-link :to="{path: 'detail', query: {id: 1}}">前往detail頁面</router-link>複製代碼

c頁面的路徑爲http://localhost:8080/#/detail?id=1,能夠看到傳了一個參數id=1,而且就算刷新頁面id也還會存在。此時在c頁面能夠經過id來獲取對應的詳情數據,獲取id的方式是this.$route.query.idcss

vue傳參方式有:query、params+動態路由傳參。html

說下二者的區別:前端

    1.query經過path切換路由,params經過name切換路由vue

// query經過path切換路由
<router-link :to="{path: 'Detail', query: { id: 1 }}">前往Detail頁面</router-link>
// params經過name切換路由
<router-link :to="{name: 'Detail', params: { id: 1 }}">前往Detail頁面</router-link>複製代碼

    2.query經過this.$route.query來接收參數,params經過this.$route.params來接收參數。java

// query經過this.$route.query接收參數
created () {
    const id = this.$route.query.id;
}

// params經過this.$route.params來接收參數
created () {
    const id = this.$route.params.id;
}複製代碼

    3.query傳參的url展示方式:/detail?id=1&user=123&identity=1&更多參數webpack

       params+動態路由的url方式:/detail/123ios

    4.params動態路由傳參,必定要在路由中定義參數,而後在路由跳轉的時候必需要加上參數,不然就是空白頁面:git

{      
    path: '/detail/:id',      
    name: 'Detail',      
    component: Detail    
},複製代碼

注意,params傳參時,若是沒有在路由中定義參數,也是能夠傳過去的,同時也能接收到,可是一旦刷新頁面,這個參數就不存在了。這對於須要依賴參數進行某些操做的行爲是行不通的,由於你總不可能要求用戶不能刷新頁面吧。 例如:

// 定義的路由中,只定義一個id參數
{
    path: 'detail/:id',
    name: 'Detail',
    components: Detail
}

// template中的路由傳參,
// 傳了一個id參數和一個token參數
// id是在路由中已經定義的參數,而token沒有定義
<router-link :to="{name: 'Detail', params: { id: 1, token: '123456' }}">前往Detail頁面</router-link>

// 在詳情頁接收
created () {
    // 如下均可以正常獲取到
    // 可是頁面刷新後,id依然能夠獲取,而token此時就不存在了
    const id = this.$route.params.id;
    const token = this.$route.params.token;
}複製代碼


本地開發環境請求服務器接口跨域的問題


上面的這個報錯你們都不會陌生,報錯是說沒有訪問權限(跨域問題)。本地開發項目請求服務器接口的時候,由於客戶端的同源策略,致使了跨域的問題。

下面先演示一個沒有配置容許本地跨域的的狀況:




能夠看到,此時咱們點擊獲取數據,瀏覽器提示咱們跨域了。因此咱們訪問不到數據。

那麼接下來咱們演示設置容許跨域後的數據獲取狀況:


注意:配置好後必定要關閉原來的server,從新npm run dev啓動項目。否則無效。



咱們在1出設置了容許本地跨域,在2處,要注意咱們訪問接口時,寫的是/api,此處的/api指代的就是咱們要請求的接口域名。若是咱們不想每次接口都帶上/api,能夠更改axios的默認配置axios.defaults.baseURL = '/api';這樣,咱們請求接口就能夠直接this.$axios.get('app.php?m=App&c=Index&a=index'),很簡單有木有。此時若是你在network中查看xhr請求,你會發現顯示的是localhost:8080/api的請求地址。這樣沒什麼大驚小怪的,代理而已:



好了,最後附上proxyTable的代碼:

proxyTable: {
      // 用‘/api’開頭,代理全部請求到目標服務器
      '/api': {
        target: 'http://jsonplaceholder.typicode.com', // 接口域名
        changeOrigin: true, // 是否啓用跨域
        pathRewrite: { //
          '^/api': ''
        }
      }
}複製代碼

注意:配置好後必定要關閉原來的server,從新npm run dev啓動項目。否則無效。


axios封裝和api接口的統一管理

axios的封裝,主要是用來幫咱們進行請求的攔截和響應的攔截。

在請求的攔截中咱們能夠攜帶userToken,post請求頭、qs對post提交數據的序列化等。

在響應的攔截中,咱們能夠進行根據狀態碼來進行錯誤的統一處理等等。

axios接口的統一管理,是作項目時必須的流程。這樣能夠方便咱們管理咱們的接口,在接口更新時咱們沒必要再返回到咱們的業務代碼中去修改接口。

因爲這裏內容稍微多一些,放在另外一篇文章,這裏送上連接


UI庫的按需加載:

爲何要使用按需加載的方式而不是一次性所有引入,緣由就很少說了。這裏以vant的按需加載爲例,演示vue中ui庫怎樣進行按需加載:

  • 安裝: cnpm i vant -S
  • 安裝babel-plugin-import插件使其按需加載:  cnpm i babel-plugin-import -D
  • 在 .babelrc文件中中添加插件配置 :

libraryDirectory { 
    
    "plugins": [ 
        // 這裏是原來的代碼部分
        // …………

        // 這裏是要咱們配置的代碼
        ["import", 
            { 
                "libraryName": "vant", 
                "libraryDirectory": "es", 
                "style": true 
            }
        ] 
    ] 
}複製代碼
  • 在main.js中按需加載你須要的插件:

// 按需引入vant組件
import {   
    DatetimePicker,   
    Button,   
    List 
} from 'vant';複製代碼
  • 使用組件:

// 使用vant組件
Vue.use(DatetimePicker)  
    .use(Button)  
    .use(List);複製代碼
  • 最後在在頁面中使用:

<van-button type="primary">按鈕</van-button>複製代碼

ps:出來vant庫外,像antiUi、elementUi等,不少ui庫都支持按需加載,能夠去看文檔,上面都會有提到。基本都是經過安裝babel-plugin-import插件來支持按需加載的,使用方式與vant的一模一樣,能夠去用一下。


如何優雅的只在當前頁面中覆蓋ui庫中組件的樣式

首先咱們vue文件的樣式都是寫在<style lang="less" scoped></style>標籤中的,加scoped是爲了使得樣式只在當前頁面有效。那麼問題來了,看圖:


咱們正常寫的全部樣式,都會被加上[data-v-23d425f8]這個屬性(如1所示),可是第三方組件內部的標籤並無編譯爲附帶[data-v-23d425f8]這個屬性。因此,咱們想修改組件的樣式,就沒轍了。怎麼辦呢,有些小夥伴給第三方組件寫個class,而後在一個公共的css文件中或者在當前頁面再寫一個沒有socped屬性的style標籤,而後直接在裏面修改第三方組件的樣式。這樣不失爲一個方法,可是存在全局污染和命名衝突的問題。約定特定的命名方式,能夠避免命名衝突。可是仍是不夠優雅。

做爲一名優()秀()的()前()端(),怎麼能容許這種狀況出現呢?好了,下面說下優雅的解決方式:

經過深度選擇器解決。例如修改上圖中組件裏的van-ellipsis類的樣式,能夠這樣作:

.van-tabs /deep/ .van-ellipsis { color: blue};
複製代碼

編譯後的結果就是:


這樣就不會給van-ellipsis也添加[data-v-23d425f8]屬性了。至此你能夠愉快的修改第三方組件的樣式了。

固然了這裏的深度選擇器/deep/是由於我用的less語言,若是你沒有使用less/sass等,能夠用>>>符號。

更多的關於深度選擇器的內容,在文章後面有介紹。


定時器問題:

我在a頁面寫一個定時,讓他每秒鐘打印一個1,而後跳轉到b頁面,此時能夠看到,定時器依然在執行。這樣是很是消耗性能的。以下圖所示:




解決方法1:

首先我在data函數裏面進行定義定時器名稱:

data() {            
    return {                              
        timer: null  // 定時器名稱          
    }        
},複製代碼

而後這樣使用定時器:

this.timer = (() => {
    // 某些操做
}, 1000)複製代碼

最後在beforeDestroy()生命週期內清除定時器:

beforeDestroy() {
    clearInterval(this.timer);        
    this.timer = null;
}複製代碼
方案1有兩點很差的地方,引用尤大的話來講就是:
  • 它須要在這個組件實例中保存這個 timer,若是能夠的話最好只有生命週期鉤子能夠訪問到它。這並不算嚴重的問題,可是它能夠被視爲雜物。
  • 咱們的創建代碼獨立於咱們的清理代碼,這使得咱們比較難於程序化的清理咱們創建的全部東西。

解決方案2:

該方法是經過$once這個事件偵聽器器在定義完定時器以後的位置來清除定時器。如下是完整代碼:

const timer = setInterval(() =>{                    
    // 某些定時器操做                
}, 500);            
// 經過$once來監聽定時器,在beforeDestroy鉤子能夠被清除。
this.$once('hook:beforeDestroy', () => {            
    clearInterval(timer);                                    
})複製代碼

方案2要感謝@zzx18023在評論區提供出的解決方案。相似於其餘須要在當前頁面使用,離開須要銷燬的組件(例如一些第三方庫的picker組件等等),均可以使用此方式來解決離開後之後在背後運行的問題。

綜合來講,咱們更推薦使用方案2,使得代碼可讀性更強,一目瞭然。若是不清楚$once、$on、$off的使用,這裏送上官網的地址教程,在程序化的事件偵聽器那裏


rem文件的導入問題:

咱們在作手機端時,適配是必需要處理的一個問題。例如,咱們處理適配的方案就是經過寫一個rem.js,原理很簡單,就是根據網頁尺寸計算html的font-size大小,基本上小夥伴們都知道,這裏直接附上代碼,很少作介紹。

;(function(c,d){var e=document.documentElement||document.body,a="orientationchange" in window?"orientationchange":"resize",b=function(){var f=e.clientWidth;e.style.fontSize=(f>=750)?"100px":100*(f/750)+"px"};b();c.addEventListener(a,b,false)})(window);複製代碼

這裏說下怎麼引入的問題,很簡單。在main.js中,直接import './config/rem'導入便可。import的路徑根據你的文件路徑去填寫。


Vue-Awesome-Swiper基本能解決你全部的輪播需求

在咱們使用的不少ui庫(vant、antiUi、elementUi等)中,都有輪播組件,對於普通的輪播效果足夠了。可是,某些時候,咱們的輪播效果可能比較炫,這時候ui庫中的輪播可能就有些力不從心了。固然,若是技術和時間上都還能夠的話,能夠本身造個比較炫的輪子。

這裏我說一下vue-awesome-swiper這個輪播組件,真的很是強大,基本能夠知足咱們的輪播需求。swiper相信不少人都用過,很好用,也很方便咱們二次開發,定製咱們須要的輪播效果。vue-awesome-swiper組件實質上基於swiper的,或者說就是能在vue中跑的swiper。下面說下怎麼使用:

  • 安裝 cnpm install vue-awesome-swiper --save
  • 在組件中使用的方法,全局使用意義不大:
// 引入組件
import 'swiper/dist/css/swiper.css' 
import { swiper, swiperSlide } from 'vue-awesome-swiper'

// 在components中註冊組件
components: {
    swiper,
    swiperSlide
}

// template中使用輪播
// ref是當前輪播
// callback是回調
// 更多參數用法,請參考文檔
<swiper :options="swiperOption" ref="mySwiper" @someSwiperEvent="callback">            
    <!-- slides -->            
    <swiper-slide><div class="item">1</div></swiper-slide>            
    <swiper-slide><div class="item">2</div></swiper-slide>            
    <swiper-slide><div class="item">3</div></swiper-slide>            
          
    <!-- Optional controls -->            
    <div class="swiper-pagination"  slot="pagination"></div>            
    <div class="swiper-button-prev" slot="button-prev"></div>            
    <div class="swiper-button-next" slot="button-next"></div>            
    <div class="swiper-scrollbar"   slot="scrollbar"></div>
</swiper>
複製代碼

// 參數要寫在data中
data() {            
    return {     
        // swiper輪播的參數           
        swiperOption: { 
            // 滾動條                   
            scrollbar: {                        
                el: '.swiper-scrollbar',                    
            }, 
            // 上一張,下一張                   
            navigation: {                        
                nextEl: '.swiper-button-next',                        
                prevEl: '.swiper-button-prev',                    
            },
            // 其餘參數…………   
        }            
    }                    
},複製代碼

swiper須要配置哪些功能需求,本身根據文檔進行增長或者刪減。附上文檔:npm文檔swiper3.0/4.0文檔,更多用法,請參考文檔說明。


打包後生成很大的.map文件的問題

項目打包後,代碼都是通過壓縮加密的,若是運行時報錯,輸出的錯誤信息沒法準確得知是哪裏的代碼報錯。 而生成的.map後綴的文件,就能夠像未加密的代碼同樣,準確的輸出是哪一行哪一列有錯能夠經過設置來不生成該類文件。可是咱們在生成環境是不須要.map文件的,因此能夠在打包時不生成這些文件:

在config/index.js文件中,設置productionSourceMap: false,就能夠不生成.map文件


fastClick的300ms延遲解決方案

開發移動端項目,點擊事件會有300ms延遲的問題。至於爲何會有這個問題,請自行百度便可。這裏只說下常見的解決思路,無論vue項目仍是jq項目,均可以使用fastClick解決。

安裝 fastClick:

cnpm install fastclick -S複製代碼

在main.js中引入fastClick和初始化:

import FastClick from 'fastclick'; // 引入插件
FastClick.attach(document.body); // 使用 fastclick複製代碼


組件中寫選項的順序

爲何選項要有統一的書寫順序呢?很簡單,就是要將選擇和認知成本最小化。

  1. 反作用 (觸發組件外的影響)

    • el
  2. 全局感知 (要求組件之外的知識)

    • name
    • parent
  3. 組件類型 (更改組件的類型)

    • functional
  4. 模板修改器 (改變模板的編譯方式)

    • delimiters
    • comments
  5. 模板依賴 (模板內使用的資源)

    • components
    • directives
    • filters
  6. 組合 (向選項裏合併屬性)

    • extends
    • mixins
  7. 接口 (組件的接口)

    • inheritAttrs
    • model
    • props/propsData
  8. 本地狀態 (本地的響應式屬性)

    • data
    • computed
  9. 事件 (經過響應式事件觸發的回調)

    • watch
    • 生命週期鉤子 (按照它們被調用的順序)
      • beforeCreate
      • created
      • beforeMount
      • mounted
      • beforeUpdate
      • updated
      • activated
      • deactivated
      • beforeDestroy
      • destroyed
  10. 非響應式的屬性 (不依賴響應系統的實例屬性)

    • methods
  11. 渲染 (組件輸出的聲明式描述)

    • template/render
    • renderError


查看打包後各文件的體積,幫你快速定位大文件

若是你是vue-cli初始化的項目,會默認安裝webpack-bundle-analyzer插件,該插件能夠幫助咱們查看項目的體積結構對比和項目中用到的全部依賴。也能夠直觀看到各個模塊體積在整個項目中的佔比。很霸道有木有~~


npm run build --report // 直接運行,而後在瀏覽器打開http://127.0.0.1:8888/便可查看複製代碼

記得運行的時候先把以前npm run dev開啓的本地關掉


路由懶加載(也叫延遲加載)

路由懶加載能夠幫咱們在進入首屏時不用加載過分的資源,從而減小首屏加載速度。

路由文件中,

非懶加載寫法:

import Index from '@/page/index/index';
export default new Router({  
    routes: [    
        { 
            path: '/', 
            name: 'Index',     
            component: Index 
        }
    ]
})複製代碼
路由懶加載寫法:

export default new Router({
  routes: [    
        { 
            path: '/', 
            name: 'Index', 
            component: resolve => require(['@/view/index/index'], resolve) 
        }
   ]
})複製代碼


開啓gzip壓縮代碼

spa這種單頁應用,首屏因爲一次性加載全部資源,全部首屏加載速度很慢。解決這個問題很是有效的手段之一就是先後端開啓gizp(其餘還有緩存、路由懶加載等等)。gizp其實就是幫咱們減小文件體積,能壓縮到30%左右,即100k的文件gizp後大約只有30k。

vue-cli初始化的項目中,是默認有此配置的,只須要開啓便可。可是須要先安裝插件:

// 2.0的版本設置不同,本文寫做時爲v1版本。v2需配合vue-cli3cnpm i compression-webpack-plugin@1.1.11 複製代碼

而後在config/index.js中開啓便可:

build: {
    // 其餘代碼
    …………
    productionGzip: true, // false不開啓gizp,true開啓
    // 其餘代碼
}複製代碼

如今打包的時候,除了會生成以前的文件,仍是生成.gz結束的gzip事後的文件。具體實現就是若是客戶端支持gzip,那麼後臺後返回gzip後的文件,若是不支持就返回正常沒有gzip的文件。

**注意:這裏前端進行的打包時的gzip,可是還須要後臺服務器的配置。配置是比較簡單的,配置幾行代碼就能夠了,通常這個操做能夠叫運維小哥哥小姐姐去搞一下,沒有運維的讓後臺去幫忙配置。


詳情頁返回列表頁緩存數據和瀏覽位置、其餘頁面進入列表頁刷新數據的實踐

這樣一個場景:有三個頁面,首頁/或者搜索頁,商品分類頁面,商品詳情頁。咱們但願從首頁進入分類頁面時,分類頁面要刷新數據,從分類進入詳情頁再返回到分類頁面時,咱們不但願刷新,咱們但願此時的分類頁面可以緩存已加載的數據和自動保存用戶上次瀏覽的位置。以前在百度搜索的基本都是keep-alive處理的,可是總有那麼一些不完善,因此本身在總結了以後進行了以下的實踐。

解決這種場景需求咱們能夠經過vue提供的keepAlive屬性。這裏直接送上另外一篇處理這個問題的傳送門


CSS的coped私有做用域和深度選擇器

你們都知道當 <style> 標籤有 scoped 屬性時,它的 CSS 只做用於當前組件中的元素。那麼他是怎麼實現的呢,你們看一下編譯先後的代碼就明白了:

編譯前:

<style scoped>
.example {
  color: red;
}
</style>複製代碼

編譯後:

<style>
.example[data-v-f3f3eg9] {
  color: red;
}複製代碼

看完你確定就會明白了,實際上是在你寫的組件的樣式,添加了一個屬性而已,這樣就實現了所謂的私有做用域。可是也會有弊端,考慮到瀏覽器渲染各類 CSS 選擇器的方式,當 p { color: red } 設置了做用域時 (即與特性選擇器組合使用時) 會慢不少倍。若是你使用 class 或者 id 取而代之,好比 .example { color: red },性能影響就會消除。因此,在你的樣式裏,進來避免直接使用標籤,取而代之的你能夠給標籤起個class名。


若是你但願 scoped 樣式中的一個選擇器可以做用得「更深」,例如影響子組件,你可使用 >>> 操做符:

<style scoped>
    .parent >>> .child { /* ... */ }
</style>複製代碼

上述代碼將會編譯成:

.parent[data-v-f3f3eg9] .child { 
    /* ... */ 
}複製代碼

而對於less或者sass等預編譯,是不支持>>>操做符的,可使用/deep/來替換>>>操做符,例如:.parent /deep/ .child { /* ... */ }


==================================

後面會繼續更新:


  • axios封裝和api接口的統一管理(已更新,在上面的連接)
  • hiper打開速度測試
  • vue數據的兩種獲取方式+骨架屏
  • 自定義組件(父子組件)的雙向數據綁定
  • 路由的拆分管理
  • mixins混入簡化常見操做
  • 打包以後文件、圖片、背景圖資源不存在或者路徑錯誤的問題
  • vue插件的開發、發佈到github、設置展現地址、發佈npm包

------------華麗麗的分割線-------------------------華麗麗的分割線-------------------------華麗麗的分割線-------------------------華麗麗的分割線-------------------------華麗麗的分割線-------------------------華麗麗的分割線-------------------------華麗麗的分割線-------------

Hiper:一款使人愉悅的性能分析工具


如上圖,是hiper工具的測試結果,從中咱們能夠看到DNS查詢耗時、TCP鏈接耗時、第一個Byte到達瀏覽器的用時、頁面下載耗時、DOM Ready以後又繼續下載資源的耗時、白屏時間、DOM Ready 耗時、頁面加載總耗時。

在咱們的編輯器終端中全局安裝:

cnpm install hiper -g複製代碼

使用:終端輸入命令:hiper 測試的網址

# 當咱們省略協議頭時,默認會在url前添加`https://`

 # 最簡單的用法
 hiper baidu.com

 # 如何url中含有任何參數,請使用雙引號括起來
 hiper "baidu.com?a=1&b=2"

 # 加載指定頁面100次
 hiper -n 100 "baidu.com?a=1&b=2"

 # 禁用緩存加載指定頁面100次
 hiper -n 100 "baidu.com?a=1&b=2" --no-cache

 # 禁JavaScript加載指定頁面100次
 hiper -n 100 "baidu.com?a=1&b=2" --no-javascript
 
 # 使用GUI形式加載指定頁面100次
 hiper -n 100 "baidu.com?a=1&b=2" -H false

 # 使用指定useragent加載網頁100次
 hiper -n 100 "baidu.com?a=1&b=2" -u "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.181 Safari/537.36"複製代碼

這段用法示例,我直接拷貝的文檔說明,具體的能夠看下文檔,這裏送上連接。當咱們項目打開速度慢時,這個工具能夠幫助咱們快速定位出到底在哪一步影響的頁面加載的速度。

平時咱們查看性能的方式,是在performance和network中看數據,記錄下幾個關鍵的性能指標,而後刷新幾回再看這些性能指標。有時候咱們發現,因爲樣本太少,受當前「網絡」、「CPU」、「內存」的繁忙程度的影響很重,有時優化後的項目反而比優化前更慢。

若是有一個工具,一次性地請求N次網頁,而後把各個性能指標取出來求平均值,咱們就能很是準確地知道這個優化是「正優化」仍是「負優化」。

hiper就是解決這個痛點的。


vue獲取數據的兩種方式的實踐+簡單骨架屏實現

在vue中獲取數據有兩種方式,引入尤大大的話就是:

  • 導航完成以後獲取:先完成導航,而後在接下來的組件生命週期鉤子中獲取數據。在數據獲取期間顯示「加載中」之類的指示。

  • 導航完成以前獲取:導航完成前,在路由進入的守衛中獲取數據,在數據獲取成功後執行導航。

從技術角度講,兩種方式都不錯 —— 就看你想要的用戶體驗是哪一種。那麼咱們來實踐一下這兩種獲取數據的方式,以及用戶體驗優化的一點思考。

1、首先是第一種:導航完成以後獲取,這種方式是咱們大部分都在使用的,(由於可能一開始咱們只知道這種方式^V^)。使用這種方式時,咱們會立刻導航和渲染組件,而後在組件的 created 鉤子中獲取數據。這讓咱們有機會在數據獲取期間展現一個 loading 狀態,還能夠在不一樣視圖間展現不一樣的 loading 狀態。獲取數據你們都會,這裏說下用戶體驗的一些東西:

  • 在數據獲取到以前,頁面組件已經加載,可是數據沒有拿到並渲染,因此在此過程當中,咱們不能加載頁面內展現數據的那塊組件,而是要有一個loading的加載中的組件或者骨架屏。
  • 當頁面數據獲取失敗,能夠理解爲請求超時的時候,咱們要展現的是斷網的組件。
  • 若是是列表頁,還要考慮到空數據的狀況,即爲空提示的組件。

那麼,咱們的頁面是要有這基本的三個部分的,放代碼:

<template>
    <div class="list">
        <!--加載中或者骨架屏-->
        <div v-if="loading">
       
        </div>

        <!--請求失敗,即斷網的提示組件-->
        <div v-if="error">
      
        </div>

        <!--頁面內容-->
        <div v-if="requestFinished" class="content">
            <!--頁面內容-->
            <div v-if="!isEmpty">
                <!--例若有個列表,固然確定還會有其餘內容-->
                <ul></ul>
            </div>

            <!--爲空提示組件-->
            <div v-else>空空如也</div>
        </div>
    </div>
</template>複製代碼

這種獲取數據的狀況下,咱們進來默認的是展現loading或者骨架屏的內容,而後若是獲取數據失敗(即請求超時或者斷網),則加載error的那個組件,隱藏其餘組件。若是數據請求成功,則加載內容的組件,隱藏其餘組件。若是是列表頁,可能在內容組件中還會有列表和爲空提示兩塊內容,因此這時候也還要根據獲取的數據來判斷是加載內容仍是加載爲空提示。

2、第二種方式:導航完成以前獲取

這種方式是在頁面的beforeRouteEnter鉤子中請求數據,只有在數據獲取成功以後纔會跳轉導航頁面。

beforeRouteEnter (to, from, next) {        
    api.article.articleDetail(to.query.id).then(res=> {            
        next(vm => {                
            vm.info = res.data;                
            vm.loadFinish = true            
        })        
    })    
},複製代碼

1. 你們都知道鉤子中beforeRouteEnter鉤子中this還不能使用,因此要想進行賦值操做或者調用方法,咱們只能經過在next()方法的回調函數中處理,這個回調函數的第一個參數就表明了this,他會在組件初始化成功後進行操做。

2. 我想,不少時候咱們的api或者axios方法都是掛載到vue的原型上的,因爲這裏使用不了this,因此只能在頁面組件內引入api或者咱們的axios。

3. 賦值操做也能夠寫在method方法中,可是調用這個賦值方法仍是vm.yourFunction()的方式。

4. 爲空提示、斷網處理等都和第一種方式同樣,可是,因爲是先獲取到數據以後再跳轉加載組件的,因此咱們不須要在預期的頁面內展現骨架屏或者loading組件。能夠,咱們須要在當前頁面進入以前,即在上一個頁面的時候有一個加載的提示,好比頁面頂部的進度條。這樣用戶體驗就比較友好了,而不至於由於請求的s速度慢一些致使半天沒反應而用戶又不知道的結果。全局的頁面頂部進度條,能夠在main.js中經過router.beforeEach(to, from, next) {}來設置,當頁面路由變化時,顯示頁面頂部的進度條,進入新路由後隱藏掉進度條。


關於怎麼添加進度條,由於在另外一篇文章已經寫了,這裏直接送上連接吧,就再也不重複浪費地方了。操做也比較簡單,可自行查閱。

其實說到了這裏,那麼骨架屏的事情也就順帶已經解決了,通常頁面骨架屏也就是一張頁面骨架的圖片,可是要注意這張圖片要儘量的小。


自定義組件(父子組件)的雙向數據綁定

說到父子組件的通訊,你們必定都不陌生了:父組件經過props向子組件傳值,子組件經過emit觸發父組件自定義事件。可是這裏要說的是父子組件使用v-model實現的通訊。相信你們在使用別人的組件庫的時候,常常是經過v-model來控制一個組件顯示隱藏的效果等,例如彈窗。下面就一步一步解開v-model的神祕面紗。抓~~穩~~嘍~~,老司機彎道要踩油門了~~~

提到v-model首先想到的就是咱們對於表單用戶數據的雙向數據綁定,操做起來很簡潔很粗暴,例如:

<input type="text" v-model="msg">

data () {            
    return {                
        msg: ''            
    }        
}複製代碼

其實v-model是個語法糖,上面這一段代碼和下面這一段代碼是同樣的效果:

<input type="text" :value="msg" @input="msg = $event.target.value">
data () {
    return {
        msg: '' 
    }        
},複製代碼

由此能夠看出,v-model="msg"實則是 :value="msg" @input="msg = $event.target.value"的語法糖。這裏其實就是監聽了表單的input事件,而後修改:value對應的值。除了在輸入表單上面可使用v-model外,在組件上也是可使用的,這點官網有提到,可是介紹的不是很詳細,致使剛接觸的小夥伴會有一種雲裏霧裏不知所云的感受。既然瞭解了v-model語法糖本質的用法,那麼咱們就能夠這樣實現父子組件的雙向數據綁定:

以上原理實現方法,寫法1:

父組件用法:

<empty v-model="msg"></empty>複製代碼

子組件寫法:

// 點擊該按鈕觸發父子組件的數據同步
<div class="share-btn" @click="confirm">肯定</div>

// 接收父組件傳遞的value值
// 注意,這種實現方法,這裏只能使用value屬性名
props: {            
    value: {                
        type: Boolean,                
        default: false            
    }        
},
methods: {            
    confirm () {                
        // 雙向數據綁定父組件:value對應的值 
        // 經過$emit觸發父組件input事件,第二個參數爲傳遞給父組件的值,這裏傳遞了一個false值 
        // 能夠理解爲最上面展現的@input="msg = $event.target.value"這個事件
        // 即觸發父組件的input事件,並將傳遞的值‘false’賦值給msg             
        this.$emit('input', false)            
    }        
}複製代碼

這種方式實現了父子組件見v-model雙向數據綁定的操做,例如你能夠試一下實現一個全局彈窗組件的操做,經過v-model控制彈窗的顯示隱藏,由於你要在頁面內進行某些操做將他顯示出來,控制其隱藏的代碼是寫在組件裏面的,當組件隱藏了對應的也要父組件對應的值改變。

以上這種方式實現的父子組件的v-model通訊,雖可行,但限制了咱們必須popos接收的屬性名爲value和emit觸發的必須爲input,這樣就容易有衝突,特別是在表單裏面。因此,爲了更優雅的使用v-model通訊而解決衝突的問題,咱們能夠經過在子組件中使用model選項,下面演示寫法2:

父組件寫法:

<empty v-model="msg"></empty>複製代碼

子組件寫法:

<div class="share-btn" @click="confirm">肯定</div>

// model選項用來避免衝突
// prop屬性用來指定props屬性中的哪一個值用來接收父組件v-model傳遞的值
// 例如這裏用props中的show來接收父組件傳遞的v-model值
// event:爲了方便理解,能夠簡單理解爲父組件@input的別名,從而避免衝突
// event的值對應了你emit時要提交的事件名,你能夠叫aa,也能夠叫bb,可是要命名要有意義哦!!!
model: {            
    prop: 'show',            
    event: 'changed'        
},
props: {
    // 因爲model選項中的prop屬性指定了,因此show接收的是父組件v-model傳遞的值            
    show: {                
        type: Boolean,                
        default: false            
    }        
},        
methods: {            
    confirm () {                
        // 雙向數據綁定父組件傳遞的值
        // 第一個參數,對應model選項的event的值,你能夠叫aa,bbb,ccc,起名隨你 
        this.$emit('changed', false)            
    }        
}複製代碼

這種實現父子組件見v-model綁定值的方法,在咱們開發中實際上是很經常使用的,特別是你要封裝公共組件的時候。

最後,實現雙向數據綁定的方式其實還有.sync,這個屬性一開始是有的,後來因爲被認爲或破壞單向數據流被刪除了,但最後證實他仍是有存在乎義的,因此在2.3版本又加回來了。

例如:父組件:

<empty :oneprop.sync="msg"></empty>

data () {
    return {
        msg: ''
    }
}複製代碼

子組件:

<div class="share-btn" @click="changeMsg">改變msg值</div>

props: {            
    oneprop: {                
        type: String,                
        default: 'hello world'
    }        
},        
methods: {            
    changeMsg () {                
        // 雙向數據流
        this.$emit('update:msg', 'helow world')           
    }        
}        複製代碼

這樣,即可以在子組件更新父組件的數據。因爲v-model只使用一次,因此當須要雙向綁定的值有多個的時候,.sync仍是有必定的使用場景的。.sync是下面這種寫法的語法糖,旨在簡化咱們的操做:

<empty
    :msg="message"
    @update:msg="message = $event"
></empty>複製代碼

掌握了組件的v-model寫法,在封裝一些公共組件的時候就又輕鬆一些了吧。

這裏再提一下:

  • vm.$emit(event ,[...args])這個api,其主要做用就是用來觸發當前實例上的事件。附加參數都會傳給監聽器回調。子組件也屬於當前實例。第一個參數:要觸發的事件名稱。後續的參數可選:即做爲參數傳遞給要觸發的事件。文檔
  • 監聽當前實例上的自定義事件,事件能夠有$emit觸發,也能經過hook監聽到鉤子函數,

vm.$on( event, callback ):一直監聽;文檔

vm.$once( event, callback ):監聽一次;文檔

vm.$off( [event, callback] ):移除監聽;文檔

監聽$emit觸發的自定義事件,上面已經有過用法了,監聽鉤子函數,在上面的定時器那塊也有演示到。監聽鉤子函數的場景使用的很少,可是仍是要知道的。

  • vm.$attrs:能夠獲取到父組件傳遞的除class和style外的全部自定義屬性。
  • vm.$listeners:能夠獲取到父組件傳遞的全部自定義事件

例如:父組件:

<empty
    :msg="message"
    :title="articleTitle"
    @confirm="func1"
    @cancel="func2"
></empty>複製代碼

就能夠在子組件中獲取父組件傳遞的屬性和事件,而不用在props中定義。子組件簡單演示以下:

created() {            
    const msg = this.$attrs.msg; // 獲取父組件傳遞的msg
    this.$listeners.confirm && this.$listeners.confirm(); //若組件傳遞事件confirm則執行
},複製代碼

這在咱們寫一些高級組件時候,會有用到的。


路由拆分管理

這裏說的路由拆分指的是將路由的文件,按照模塊拆分,這樣方便路由的管理,更主要的是方便多人開發。具體要不要拆分,那就要視你的項目狀況來定了,若是項目較小的話,也就一二十個路由,那麼是拆分是很是不必的。但假若你開發一些功能點較多的商城項目,路由能夠會有一百甚至幾百個,那麼此時將路由文件進行拆分是頗有必要的。否則,你看着index.js文件中一大長串串串串串串的路由,也是很糟糕的。


首先咱們在router文件夾中建立一個index.js做爲路由的入口文件,而後新建一個modules文件夾,裏面存放各個模塊的路由文件。例如這裏儲存了一個vote.js投票模塊的路由文件和一個公共模塊的路由文件。下面直接上index.js吧,然後在簡單介紹:

import Vue from 'vue'
import Router from 'vue-router'

// 公共頁面的路由文件
import PUBLIC from './modules/public' 
// 投票模塊的路由文件
import VOTE from './modules/vote' 

Vue.use(Router)

// 定義路由
const router = new Router({  
    mode: 'history',  
    routes: [    
        ...PUBLIC,    
        ...VOTE,  
    ]
})

// 路由變化時
router.beforeEach((to, from, next) => {    
    if (document.title !== to.meta.title) {        
        document.title = to.meta.title;    
    }    
    next()
})

// 導出
export default router複製代碼

首先引入vue和router最後導出,這就很少說了,基本的操做。

這裏把router.beforeEach的操做寫了router的index.js文件中,有些人可能會寫在main.js中,這也沒有錯,只不過,我的而言,既然是路由的操做,仍是放在路由文件中管理更好些。這裏就順便演示了,如何在頁面切換時,自動修改頁面標題的操做。

然後引入你根據路由模塊劃分的各個js文件,而後在實例化路由的時候,在routes數組中,將導入的各個文件經過結構賦值的方法取出來。最終的結果和正常的寫法是同樣的。

而後看下咱們導入的vote.js吧:

/** 
 * 投票模塊的router列表  
 */

export default [    
    // 投票模塊首頁    
    {        
        path: '/vote/index',        
        name: 'VoteIndex',        
        component: resolve => require(['@/view/vote/index'], resolve),        
        meta: {            
            title: '投票'        
        }    
    },    
    // 詳情頁    {        
    path: '/vote/detail',        
    name: 'VoteDetail',        
    component: resolve => require(['@/view/vote/detail'], resolve),
    meta: {            
        title: '投票詳情'        
    }    
}] 複製代碼

這裏就是將投票模塊的路由放在一個數組中導出去。整個路由拆分的操做,不是vue的知識,就是一個es6導入導出和結構的語法。具體要不要拆分,仍是因項目和環境而異吧。

這裏的路由用到了懶加載路由的方式,若是不清楚,文字上面有介紹到。

還有這裏的meta元字段中,定義了一個title信息,用來存儲當前頁面的頁面標題,即document.title。

mixins混入簡化常見操做

咱們在開發中常常會遇到金錢保留兩位小數,時間戳轉換等操做。每次咱們會寫成一個公共函數,而後在頁面裏面的filters進行過濾。這種方法每次,可是感受每次須要用到,都要寫一遍在filters,也是比較煩呢!!!可是,咱們猿類的極致追究就是懶呀,那這怎麼能行~~~

兄弟們,抄傢伙!上mixins!!!

import { u_fixed } from './tool'

const mixins = {    
    filters: {        
        // 保留兩位小數        
        mixin_fixed2 (val) {            
            return u_fixed(val)        
        },
        // 數字轉漢字,16000 => 1.60萬        
        mixin_num2chinese (val) {            
            return val > 9999 ? u_fixed(val/10000) + '萬' : val;        
    }    
}}
export default mixins複製代碼

新建一個mixins.js,把咱們須要混入的內容都寫在裏面,例如這裏混入了filters,把經常使用的幾個操做寫在了裏面,你們能夠自行擴展。

這樣的話,在咱們須要的頁面import這個js,而後聲明一下混入就好,然後就能夠像正常的方式去使用就行了。


例如,我如今能夠直接在頁面內使用咱們的過濾操做{{1000 | mixin_fixed2}}


打包以後文件、圖片、背景圖資源不存在或者路徑錯誤的問題

先看下項目的config文件夾下的index.js文件,這個配置選項就好使咱們打包後的資源公共路徑,默認的值爲‘/’,即根路徑,因此打包後的資源路徑爲根目錄下的static。由此問題來了,若是你打包後的資源沒有放在服務器的根目錄,而是在根目錄下的mobile等文件夾的話,那麼打包後的路徑和你代碼中的路徑就會有衝突了,致使資源找不到。

因此,爲了解決這個問題,你能夠在打包的時候把上面這個路徑由‘/’的根目錄,改成‘./’的相對路徑。


這樣的的話,打包後的圖片啊js等路徑就是‘./static/img/asc.jpg’這樣的相對路徑,這就無論你放在哪裏,都不會有錯了。可是,凡是都有可是~~~~~這裏一切正常,可是背景圖的路徑仍是不對。由於此時的相對就變成了static/css/文件夾下的static/img/xx.jpg,可是實際上static/css/文件夾下沒有static/img/xx.jpg,即static/css/static/img/xx.jpg是不存在的。此時相對於的當前的css文件的路徑。因此爲了解決這個問題,要把咱們css中的背景圖的加個公共路徑‘../../’,即讓他往上返回兩級到和index.html文件同級的位置,那麼此時的相對路徑static/img/xx.jpg就能找到對應的資源了。那麼怎麼修改背景圖的這個公共路徑呢,由於背景圖是經過loader解析的,因此天然在loader的配置中修改,打開build文件夾下的utils文件,找到exports.cssLoaders的函數,在函數中找到對應下面這些配置:

找到這個位置,添加一上配置,就是上圖紅框內的代碼,就能夠把它的公共路徑修改成往上返回兩級。這樣再打包看下,就ok了!

最後再鄭重說一點,若是你的路由模式是history的,那麼打包放在服務器,必需要後臺服務器的配合,具體的能夠看官方文檔,這點很重要。否則你會發現白屏啊等各類莫名其妙的問題。牢記!!!


vue插件的開發、發佈到github、設置展現地址、發佈npm包

對於平時咱們經常使用的一些組件,咱們能夠把它封裝成插件,而後發佈到github上,最後再發布成npm包,這樣之後即可以直接從npm安裝插件到咱們的項目中,省去了咱們拷貝的過程了,還能給別人分享呢!

因爲插件的這一塊內容比較多,我暫且放在另一篇文章吧,這裏呢就附上連接吧

相關文章
相關標籤/搜索