專案準備
目錄結構
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
,並transform
的scale
縮小一半,原先的元素相對定位,新做的border
絕對定位。
樣式重置
網上有很多reset.css
找一份引入到專案之中。
輪播
佔位
圖片是可替代資源,在頁面顯然時,會先將頁面中靜態的內容渲染上去,等資料返回後,在進行重新渲染,這樣頁面就會出現抖動,影響使用者體驗,同時效能也比較低。
可以用下面的css
程式碼對這些可替換資源先進行佔位,頁面大體框架在第一次渲染後就能呈現給使用者,資料獲取到後,替換相應的內容就可,就不會出現抖動了。
.icon-img
overflow: hidden
width: 100%
height: 0
padding-bottom: 100%
複製程式碼
樣式穿透
在子元件中實現在這樣的佈局,需要用到樣式穿透,不然是無法滾動下半部分的。
.icons >>> .swiper-container
height: 0
padding-bottom:50%
複製程式碼
多頁
現在上面的輪播,一頁上有8
個icon
,此時如果需求變成了9
個,怎麼樣才能做到在不改動程式碼的前提下,能實現任意數量的icon
可以用計算屬性computed
對iconList
進行監聽:
- 在
Home.vue
首先定義了iconList: []
通過屬性傳遞給了Icons.vue
元件。 - 在
Icons.vue
中通過props
接收到了iconList
資料。 - 使用
computed
之所以能對iconList
監聽,是因為剛開始傳遞進來的iconList
是空陣列,當獲取到到資料之後,在傳遞過來iconList
是有值的,iconList
一旦發生了變化,computed
就能捕捉到。 computed
中還有一個計算屬性showIconList
,它的用途是:- 剛開始渲染的時候,由於
iconList
是一個空陣列,直接將空陣列渲染上去了 - 當有了資料之後,又會進行第二次渲染,此時就會看到,輪播始終在最後一頁
- 使用
v-if=showIconList
是為了不讓它在空陣列時渲染,而是要等到有資料後在渲染 - 所以
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
}
}
複製程式碼
城市選擇
兄弟元件通訊
這塊內容比較多,重新寫了一遍文章,介紹了兩種資料傳遞的方式,城市選擇的這邊用的是vuex
。
Vue 中非父子元件間的傳值
節流
手指在城市字母表中滑動時,會觸發無數次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)
}
}
複製程式碼
這裡設定的週期是10ms
,10ms
這個程式碼只會執行一次,大大優化了效能
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
標籤後,會有兩個生命週期函式分別是:activated
、deactivated
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-alive
的exclude
屬性,給它預設設定為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
檔案中找到resolve
下alias
,它可以對路徑進行簡化操作,專案中引入這個檔案只需要寫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
時,都是在執行scripts
下dev
指令,只需要在它上面加上--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
資料,這塊知識在後面還得要跟進學習。