去哪兒網專案學習總結

天天天天天發表於2019-03-01

專案準備

目錄結構

readme              //專案的說明檔案
package.json         //第三方依賴包配置
package.lock.json    //幫助我們去確定安裝的第三方依賴包的具體的版本,保持團隊程式設計的統一
license             //開源協議的說明
index.html          //專案預設的首頁模版檔案
.postcssrc.js        //對 postcss 的配置項
.gitignore          //不需要上傳到 git 上的檔案管理 
.eslintrc.js        //對寫的程式碼檢測是否標準做一個檢測
.eslintignore       //配置不需要 eslintrc 檢測工具檢測的檔案
.editorconfig       //配置編輯器總風格統一的自動化格式的語法
.babelrc            //專案寫的程式碼是 Vue 的大檔案元件的程式碼的寫法,所以需要通過 babel 這種語法解析器做一些語法上的轉換,最終轉換成瀏覽器能夠編譯執行的程式碼,babel 需要做額外配置時,就放在檔案裡面
static                  //static 目錄放的是靜態資源,要用到的靜態圖片啊或者後續需要模擬的 json 資料
node_modules                 //專案中需要用到的第三方 node 包
src                         //放的是專案的原始碼
src/main.js                  //整個專案的入口檔案
src/app.vue                 //整個專案最原始的根元件
src/router/index.js          //專案的路由放置位置
src/components               //專案中要用到的小元件
src/assets                  //專案中需要用到的圖片
config                      //放置專案配置檔案
config/index.js              //放基礎配置
config/dev.ent.js            //開發環境配置資訊
config/prod.ent.js           //線上環境配置資訊
build                      //放置專案打包的 webpack 配置資訊,vue-cli 會自動構建
build/webpack.base.conf.js   //基礎的 webpack 配置資訊
build/webpack.dev.conf.js    //開發環境的 webpack 配置資訊
build/webpack.prod.conf.js   //線上環境的 webpack 配置資訊
複製程式碼

移動端300ms延遲

移動端瀏覽器click事件為什麼會有300ms的延遲呢?因為在手機上有個雙擊方案 —— 在手機上快速點選兩下,實現頁面放大;再次雙擊,恢復到原始比例。

那它是如何實現的呢?瀏覽器在捕捉到第一次點選事件後,會等待一段時間,如果在這段時間內,使用者沒有再次進行點選操作的話,就執行單擊事件;如果使用者進行了第二次點選操作的話,就會執行雙擊事件。這段等待的時間大約300ms

如何解決這個延遲呢?有很多方法,這裡推薦兩種比較簡單的方法:

  • <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1,minimum-scale=1, user-scalable=no">

    width=device-width 寬度為裝置寬度 initial-scale=1 初始比例為1 maximum-scale=1 最大比例為1 minimum-scale=1 最小比例為1 user-scalable=no 使用者不能進行放大縮小

  • 引入第三方庫fastclick

    • npm install fastclick --save
    • 因為所有頁面都要引入,所以在入口處統一引入就可以了import FastClick from 'fastclick'
    • FastClick.attach(document.body)

1px畫素問題

紅色邊框是用border-bottom: 1px solid red;寫的,在手機上明顯可以看出它不是1px

去哪兒網專案學習總結

怎麼解決這個問題呢?

方法有好幾種,這裡推薦一種:偽類 + transform 看程式碼:

.border1
    height: .5rem
    position: relative
.border1:before
    position: absolute
    top:-.5rem
    left:0
    content: ''
    width:100%
    height:1px
    border-top:1px solid rgba(0,0,0,.3)
    transform: scaleY(0.5)
複製程式碼

這種方式的原理很簡單,就是把原先元素的border去掉,然後利用:before或者:after重做border ,並transformscale縮小一半,原先的元素相對定位,新做的border絕對定位。

樣式重置

網上有很多reset.css找一份引入到專案之中。

輪播

去哪兒網專案學習總結

佔位

圖片是可替代資源,在頁面顯然時,會先將頁面中靜態的內容渲染上去,等資料返回後,在進行重新渲染,這樣頁面就會出現抖動,影響使用者體驗,同時效能也比較低。

可以用下面的css程式碼對這些可替換資源先進行佔位,頁面大體框架在第一次渲染後就能呈現給使用者,資料獲取到後,替換相應的內容就可,就不會出現抖動了。

.icon-img
    overflow: hidden
    width: 100%
    height: 0
    padding-bottom: 100%
複製程式碼

樣式穿透

在子元件中實現在這樣的佈局,需要用到樣式穿透,不然是無法滾動下半部分的。

.icons >>> .swiper-container
  height: 0
  padding-bottom:50%
複製程式碼

多頁

現在上面的輪播,一頁上有8icon,此時如果需求變成了9個,怎麼樣才能做到在不改動程式碼的前提下,能實現任意數量的icon

可以用計算屬性computediconList進行監聽:

  1. Home.vue首先定義了iconList: []通過屬性傳遞給了Icons.vue元件。
  2. Icons.vue中通過props接收到了iconList資料。
  3. 使用computed之所以能對iconList監聽,是因為剛開始傳遞進來的iconList是空陣列,當獲取到到資料之後,在傳遞過來iconList是有值的,iconList一旦發生了變化,computed就能捕捉到。
  4. computed中還有一個計算屬性showIconList,它的用途是:
    1. 剛開始渲染的時候,由於iconList是一個空陣列,直接將空陣列渲染上去了
    2. 當有了資料之後,又會進行第二次渲染,此時就會看到,輪播始終在最後一頁
    3. 使用v-if=showIconList是為了不讓它在空陣列時渲染,而是要等到有資料後在渲染
    4. 所以swiper在初次建立是應該要用完整的資料來建立,而不要用空資料建立

程式碼:

<swiper :options="swiperOption">
  <swiper-slide v-for="(page,index) of pages" :key="index" v-if="showIconList">
    <div class="icon" v-for="item of page" :key="item.id">
      <div class="icon-img">
        <img class="icon-img-content" :src="item.iconUrl" alt="">
      </div>
      <p class="icon-desc">{{item.desc}}</p>
    </div>
  </swiper-slide>
</swiper>
computed: {
    pages () {
      const pages = []
      this.iconList.forEach((item, index) => {
        const page = Math.floor(index / 8)          //每頁是 8 個,index / 8 能獲取到頁數
        if (!pages[page]) {     //初始化每一項
          pages[page] = []
        }
        pages[page].push(item)  //變成新陣列
      })
      return pages
   },
   showIconList () {
      return this.iconList.length
   }
}
複製程式碼

去哪兒網專案學習總結

城市選擇

兄弟元件通訊

這塊內容比較多,重新寫了一遍文章,介紹了兩種資料傳遞的方式,城市選擇的這邊用的是vuexVue 中非父子元件間的傳值

節流

手指在城市字母表中滑動時,會觸發無數次handleTouchMove這個函式,這就對效能影響很大。

函式節流:通過設定一個時間週期,只要在這個週期內函式就不執行。

實現方法:

ihandleTouchMove (e) {
  if (this.touchStatus) {
    if (this.timer) {
      clearTimeout(this.timer)
    }
    this.timer = setTimeout(() => {
      const touchY = e.touches[0].clientY - 79
      const index = Math.floor((touchY - this.startY) / 20)
      if (index >= 0 && index < this.letters.length) {
        this.$emit('change', this.letters[index])
      }
    }, 10)
  }
}
複製程式碼

這裡設定的週期是10ms10ms這個程式碼只會執行一次,大大優化了效能

keep-alive優化請求

只傳送一次請求

每次點選城市或者回到首頁時,都會重新傳送一個ajax請求,因為當路由切換的時候,這個元件就會被重新渲染,元件一被重新渲染,mounted這個鉤子函式就會被執行。這樣就會對效能造成比較大的影響。

Vue 也考慮到了這一點,為我們提供了一個keep-alive的標籤。

<keep-alive>
   <router-view/>
</keep-alive>
複製程式碼

路由的內容被載入過一次之後,就把路由的內容放到記憶體之中,下次在進這個路由的時候不需要再重新渲染元件了,只需你從內容裡把以前的內容拿出來顯示就可以了。

城市改變在傳送請求

按照上面這樣優化,當我改變城市時,它也不會傳送請求,因為這一塊用的是記憶體裡的資料,那麼這個選擇曾是功能就變得有名無實,那該怎麼改進呢?

當我們使用了keep-alive標籤後,會自動執行鉤子函式activated,而mounted鉤子函式是不會被執行的。

activated () {
    if (this.lastCity !== this.city) {
      this.lastCity = this.city
      this.getHomeInfo()
    }
}
複製程式碼

詳情頁

全域性事件

詳情頁繫結了一個全域性事件,當我在詳情頁面中滾動,這個樣寫沒有問題,但是當我去到其他頁面,在滾動時,你就會發現,剛剛你繫結在詳情頁中的滾動事件,在這個頁面也被執行了,這肯定是有問題的。

其實在我們使用了keep-alive標籤後,會有兩個生命週期函式分別是:activateddeactivated

activated:頁面展示的時候被執行

deactivated:頁面被隱藏或者頁面即將被替換成新的頁面時被執行

activated () {
    window.addEventListener('scroll', this.handleScroll)
},
deactivated () {
    window.removeEventListener('scroll', this.handleScroll)
}
複製程式碼

這段程式碼是頁面被展示的執行scroll,頁面被隱藏的時候移除scroll事件

遞迴元件

遞迴元件就是在我元件的自身去呼叫元件的自身

假如說現在有這樣的資料結構,一級標題,二級標題,三級標題,如何實現呢?

data () {
    return {
        "categoryList": [{
            "title": "成人票",
            "children": [{
                "title": "成人三館聯票",
                "children": [{
                    "title": "成人三館聯票 - 某一銷售店"
                }]
            }, {
                "title": "成人五館聯票"
            }]
        }, {
            "title": "兒童票"
        }, {
            "title": "學生票"
        }, {
            "title": "特惠票"
        }]
    }
}
複製程式碼

對一層標題用v-for來進行迴圈,二、三層標題該怎麼顯示出來呢?在寫元件的時候,都會寫一個name的屬性,它其中一個用途就是——遞迴元件

<div
    class="item"
    v-for="(item, index) of categoryList"
    :key="index"
>
    <div class="item-title">
      <span class="item-title-icon"></span>
      {{item.title}}
    </div>
    <div class="item-title-children" v-if="item.children">  //判斷是否有資料中是否有 children 這個屬性,如果有就使用遞迴元件
      <detail-list :categoryList="item.children"></detail-list>     //把 children 傳給遞迴元件
    </div>
 </div>
複製程式碼

如圖所示:

去哪兒網專案學習總結

keep-alive不快取

Detail.vue頁面中,當我點選了其他景點後,它也是不會傳送請求的,那麼Detail頁面就不會重新渲染了。

可以使用keep-aliveexclude屬性,給它預設設定為Detail,用途是每次進入Detail頁面都會傳送請求

<keep-alive exclude="Detail">   //使用 exclude 屬性,可以設定不需要快取的頁面
   <router-view/>
</keep-alive>
複製程式碼

元件中name名字的用途

元件中name這個值到底是幹什麼用的呢:

  • 遞迴元件可以用到
  • 當你相對某個頁面想取消快取的時候會用到
  • 在 Vue 的開發除錯工具中會用到

webpack別名設定

使用alias

專案中有許多地方需要引入一些公用樣式,此專案樣式是用stylus寫了,比如很多地方都需要用到主題色,統一寫在一個檔案中後期維護很方便。但是引入這個檔案很麻煩:

../../../assets/styles/varible.css
複製程式碼

如果每個頁面都這樣引入檔案,一方面寫的不優雅,另一方面維護也不方便。

module.exports = {
  ...
  resolve: {
    extensions: ['.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js',
      '@': resolve('src'),
      'styles': resolve('src/assets/styles')
    }
  }
}
複製程式碼

build/webpack.base.config.js檔案中找到resolvealias,它可以對路徑進行簡化操作,專案中引入這個檔案只需要寫styles/varible.css即可。

路徑分配

在自己開發中,經常需要自己moke資料`

axios.get('/api/detail.json', {
    params: {
      id: this.$route.params.id
    }
}).then(this.getDetailInfoSucc)
複製程式碼

這樣寫路徑是訪問不到自己mock的資料的,那應該怎麼寫呢?

axios.get('/static/mock/detail.json', {
    params: {
      id: this.$route.params.id
    }
}).then(this.getDetailInfoSucc)
複製程式碼

/api改成/static/mock/這樣訪問到我們本地的資料了,但是這樣有風險的,上線前你需要改回/api,很容易出錯,造成bug

module.exports = {
  dev: {
    ...
    proxyTable: {
      '/api':{
        target: 'http://localhost:8080',
        pathRewrite: {
          '^/api':'/static/mock'
        }
      }
    }
 }
複製程式碼

config/index.js檔案中找到dev下的proxyTable,它可以代理路徑,我們在專案中寫/api,通過proxyTable可以自動找到/static/mock這個目錄。

移動端可以訪問

當手機上用本地的ip地址訪問專案時被拒絕了,這是因為前端專案是通過webpack-dev-server啟動的,webpack-dev-server預設不支援ip的形式訪問頁面,這就需要把它預設的配置項做一個修改。

"scripts": {
    "dev": "webpack-dev-server --host 0.0.0.0 --inline --progress --config build/webpack.dev.conf.js",
    "start": "npm run dev",
    "lint": "eslint --ext .js,.vue src",
    "build": "node build/build.js"
}
複製程式碼

當每次執行npm run start或者npm run dev時,都是在執行scriptsdev指令,只需要在它上面加上--host 0.0.0.0就可以了

總結

技術選型

去哪兒網專案學習總結

前端路由

去哪兒網專案學習總結

學到了什麼

  • 流程化開發
    • 瞭解了程式碼規範,編寫可維護程式碼
    • 學會了用git管理程式碼,用分支開發
    • 學會了元件化、模組化、工程化的開發模式
    • 掌握使用Vue-cli腳手架初始化Vue.js專案
    • 學會es6 + eslint的開發方式
    • mock 資料,前後端分離
  • 設計思想與模式
    • 函式節流
    • 元件化開發
    • 程式碼規範
  • 第三方庫使用
    • vue-awesome-swiper輪播元件
    • fastclick解決點選延遲問題
    • better-scroll體驗更好的頁面滾動
    • babel-polyfill解決proxyTable相容問題

不足之處

缺乏webpack及前後端互動相關的知識,在部署到github上出現了問題,在打包後無法訪問到mock資料,這塊知識在後面還得要跟進學習。

專案截圖

去哪兒網專案學習總結
去哪兒網專案學習總結
去哪兒網專案學習總結

參考資源

  1. fastclick.js解決移動端點選事件反應慢問題
  2. 最常用的移動端1畫素邊框解決方法

相關文章