Vue實戰專案開發--城市列表頁面
城市選擇頁面路由配置
- 建立一個city-router的分支,用於城市列表的功能實現
- 路由配置,修改router/index.js的內容
import Vue from 'vue' import Router from 'vue-router' Vue.use(Router) export default new Router({ routes: [ { path: '/', name: 'Home', component: () => import( '@/pages/home/Home') }, { path: '/city', name: 'City', component: () => import( '@/pages/city/City') } ] })
然後在pages資料夾下新建city資料夾,並建立City.vue,最後在city資料夾下新建components資料夾,然後建立City.vue的頂部Header.vue
- City.vue:
<template> <div> <city-header></city-header> </div> </template> <script> import CityHeader from './components/Header' export default { name:'City', components: { CityHeader }, data () { return { } } } </script> <style lang="stylus" scoped> </style>
Header.vue:
<template> <div class="header"> 城市選擇 <router-link to='/'> <div class="iconfont header-back"></div> </router-link> </div> </template> <script> export default { name:'CityHeader', data () { return { } } } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .header position relative height $headerHeight line-height $headerHeight text-align center color #fff background $bgColor font-size .32rem .header-back position absolute top 0 left 0 width .64rem text-align center font-size .4rem color #ffffff </style>
- 實現點選首頁的header部分的成都進入城市列表和點選城市列表的返回按鈕返回首頁,這裡使用router-link實現的,router-link類似a標籤
<router-link to='/city'> <div class="header-right"> {{this.city}} <i class="iconfont arrow-icon"></i> </div> </router-link>
<router-link to='/'> <div class="iconfont header-back"></div> </router-link>
- 程式碼優化:這裡把頂部的height在varibles.styl檔案中定義為一個常量,方便後期修改
$headerHeight = .86rem
使用
@import '~styles/varibles.styl' (這裡需要先引入才可以使用) height $headerHeight line-height $headerHeight
最後,把程式碼提交到GitHub上
搜尋框佈局
- 建立city-search分支,然後把本地的專案改在新建的這個分支上進行開發
- 在components中新建Search.vue檔案
<template> <div class="search"> <input class="search-input" type="text" placeholder="輸入城市名或拼音"> </div> </template> <script> export default { name:'CitySearch', data () { return { } } } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .search height .72rem padding 0 .1rem background $bgColor .search-input box-sizing border-box width 100% height .62rem padding 0 .1rem line-height .62rem text-align center border-radius .06rem color #666666 </style>
然後在City.vue中引入並使用
<template> <div> <city-header></city-header> <city-search></city-search> </div> </template> <script> import CityHeader from './components/Header' import CitySearch from './components/Search' export default { name:'City', components: { CityHeader, CitySearch }, data () { return { } } } </script> <style lang="stylus" scoped> </style>
然後搜尋框佈局就完成了,最後把程式碼提交到GitHub上
列表佈局
- 建立city-list分支,然後把本地的專案改在新建的這個分支上進行開發
- 在components中新建List.vue檔案
<template> <div class="list"> <div class="area"> <div class="title border-topbottom">當前城市</div> <div class="button-list"> <div class="button-wrapper"> <div class="button">成都</div> </div> </div> </div> <div class="area"> <div class="title border-topbottom">熱門城市</div> <div class="button-list"> <div class="button-wrapper"> <div class="button">成都</div> </div> <div class="button-wrapper"> <div class="button">成都</div> </div> <div class="button-wrapper"> <div class="button">成都</div> </div> <div class="button-wrapper"> <div class="button">成都</div> </div> </div> </div> <div class="area"> <div class="title border-topbottom">A</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> <div class="area"> <div class="title border-topbottom">B</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> <div class="area"> <div class="title border-topbottom">C</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> <div class="area"> <div class="title border-topbottom">D</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> </div> </template> <script> export default { name:'CityList', } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .44rem background #eee padding-left .2rem color #666 font-size .26rem .button-list padding .1rem .6rem .1rem .1rem overflow hidden .button-wrapper float left width 33.33% .button margin .1rem padding .1rem 0 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem </style>
然後在City.vue中引入並使用
<template> <div> <city-header></city-header> <city-search></city-search> <city-list></city-list> </div> </template> <script> import CityHeader from './components/Header' import CitySearch from './components/Search' import CityList from './components/List' export default { name:'City', components: { CityHeader, CitySearch, CityList } } </script> <style lang="stylus" scoped> </style>
然後列表佈局就完成了,最後把程式碼提交到GitHub上
Better-scroll的使用及字母表佈局
- 安裝better-scroll外掛
npm install better-scroll --save
- 修改List.vue的程式碼
<div class="list" ref='wrapper'> (使用ref來獲取當前元素) <div> <- 這裡放之前的內容 -> </div> </div>
引入並使用
import BScroll from 'better-scroll' export default { name:'CityList', mounted () { this.scroll = new BScroll(this.$refs.wrapper) } }
完整程式碼
<template> <div class="list" ref='wrapper'> <div> <div class="area"> <div class="title border-topbottom">當前城市</div> <div class="button-list"> <div class="button-wrapper"> <div class="button">成都</div> </div> </div> </div> <div class="area"> <div class="title border-topbottom">熱門城市</div> <div class="button-list"> <div class="button-wrapper"> <div class="button">成都</div> </div> <div class="button-wrapper"> <div class="button">成都</div> </div> <div class="button-wrapper"> <div class="button">成都</div> </div> <div class="button-wrapper"> <div class="button">成都</div> </div> </div> </div> <div class="area"> <div class="title border-topbottom">A</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> <div class="area"> <div class="title border-topbottom">B</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> <div class="area"> <div class="title border-topbottom">C</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> <div class="area"> <div class="title border-topbottom">D</div> <div class="item-list"> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> <div class="item border-bottom">阿拉爾</div> </div> </div> </div> </div> </template> <script> import BScroll from 'better-scroll' export default { name:'CityList', mounted () { this.scroll = new BScroll(this.$refs.wrapper) } } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .54rem background #eee padding-left .2rem color #666 font-size .26rem .button-list padding .1rem .6rem .1rem .1rem overflow hidden .button-wrapper float left width 33.33% .button margin .1rem padding .1rem 0 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem </style>
better-scroll就引入並使用完成了
- 字母表佈局:在componens資料夾下新建Alphabet.vue
<template> <ul class="list"> <li class="item">A</li> <li class="item">A</li> <li class="item">A</li> <li class="item">A</li> <li class="item">A</li> </ul> </template> <script> export default { name:'CityAlphabet', } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .list display flex flex-direction column justify-content center position absolute top 1.58rem right 0 bottom 0 width .4rem .item line-height .4rem text-align center color $bgColor </style>
然後在City.vue中引入:
<template> <div> <city-header></city-header> <city-search></city-search> <city-list></city-list> <city-alphabet></city-alphabet> </div> </template> <script> import CityHeader from './components/Header' import CitySearch from './components/Search' import CityList from './components/List' import CityAlphabet from './components/Alphabet' export default { name:'City', components: { CityHeader, CitySearch, CityList, CityAlphabet }, data () { return { } } } </script> <style lang="stylus" scoped> </style>
字母表佈局就完成了,最後把程式碼提交到線上
頁面的動態資料渲染
- 建立city-Ajax分支,然後把本地的專案改在新建的這個分支上進行開發
- 使用Ajax模擬網路請求,在static/mock下新建一個city.json(有需要的可以在我的GitHub上拉取),在使用axios來模擬請求資料
- City.vue的js部分:
<script> import axios from 'axios' import CityHeader from './components/Header' import CitySearch from './components/Search' import CityList from './components/List' import CityAlphabet from './components/Alphabet' export default { name:'City', components: { CityHeader, CitySearch, CityList, CityAlphabet }, data () { return { cities: {}, hotCities: [] } }, methods: { getCityInfo () { axios.get('/api/city.json') .then(this.handleGetCityInfoSucc) }, handleGetCityInfoSucc (res) { console.log(res) res = res.data if(res.ret && res.data){ const data = res.data this.cities = data.cities this.hotCities = data.hotCities } } }, mounted () { this.getCityInfo() } } </script>
City.vue:
<template> <div> <city-header></city-header> <city-search></city-search> <city-list :cities="cities" :hot="hotCities"></city-list> <city-alphabet :cities="cities"></city-alphabet> </div> </template> <script> import axios from 'axios' import CityHeader from './components/Header' import CitySearch from './components/Search' import CityList from './components/List' import CityAlphabet from './components/Alphabet' export default { name:'City', components: { CityHeader, CitySearch, CityList, CityAlphabet }, data () { return { cities: {}, hotCities: [] } }, methods: { getCityInfo () { axios.get('/api/city.json') .then(this.handleGetCityInfoSucc) }, handleGetCityInfoSucc (res) { console.log(res) res = res.data if(res.ret && res.data){ const data = res.data this.cities = data.cities this.hotCities = data.hotCities } } }, mounted () { this.getCityInfo() } } </script> <style lang="stylus" scoped> </style>
List.vue:
<template> <div class="list" ref='wrapper'> <div> <div class="area"> <div class="title border-topbottom">當前城市</div> <div class="button-list"> <div class="button-wrapper"> <div class="button">成都</div> </div> </div> </div> <div class="area"> <div class="title border-topbottom">熱門城市</div> <div class="button-list"> <div class="button-wrapper" v-for="item of hot" :key="item.id"> <div class="button">{{item.name}}</div> </div> </div> </div> <div class="area" v-for="(item,key) of cities" :key="key"> <div class="title border-topbottom">{{key}}</div> <div class="item-list"> <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">{{innerItem.name}}</div> </div> </div> </div> </div> </template> <script> import BScroll from 'better-scroll' export default { name:'CityList', props: { hot: Array, cities: Object }, mounted () { this.scroll = new BScroll(this.$refs.wrapper) } } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .54rem background #eee padding-left .2rem color #666 font-size .26rem .button-list padding .1rem .6rem .1rem .1rem overflow hidden .button-wrapper float left width 33.33% .button margin .1rem padding .1rem 0 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem </style>
Alphabet.vue:
<template> <ul class="list"> <li class="item" v-for="(item,key) of cities" :key="key">{{key}}</li> </ul> </template> <script> export default { name:'CityAlphabet', props: { cities: Object } } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .list display flex flex-direction column justify-content center position absolute top 1.58rem right 0 bottom 0 width .4rem .item line-height .4rem text-align center color $bgColor </style>
- 最後看一下最後的效果,記住把程式碼提交到GitHub上
兄弟組建間聯動
- 實現點選右側字母,左邊的內容跟著字母變化
就是點選H字母,城市列表就跳到H首字母的城市列表
這裡是通過在Alphabet.vue監聽點選字母的點選事件,然後把點選對應的元素的值傳給父元件,父元件再將這個值傳給List.vue子元件的
Alphabet.vue(增加了handleLetterClick,通過emit把值傳給父元件的):
<template>
<ul class="list">
<li
class="item"
v-for="(item,key) of cities"
:key="key"
@click="handleLetterClick"
>
{{key}}
</li>
</ul>
</template>
<script>
export default {
name:'CityAlphabet',
props: {
cities: Object
},
methods: {
handleLetterClick (e) {
this.$emit('change',e.target.innerText)
}
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.list
display flex
flex-direction column
justify-content center
position absolute
top 1.58rem
right 0
bottom 0
width .4rem
.item
line-height .4rem
text-align center
color $bgColor
</style>
City.vue(通過@change="handleLetterChange"來接受Alphabet.vue子元件傳過來的值,並在data中定義letter值,在前面的方法把從子元件得到的值賦給data中定義好的letter,然後把這個值通過屬性傳給List.vue子元件):
<template>
<div>
<city-header></city-header>
<city-search></city-search>
<city-list
:cities="cities"
:hot="hotCities"
:letter="letter"
></city-list>
<city-alphabet
:cities="cities"
@change="handleLetterChange"
></city-alphabet>
</div>
</template>
<script>
import axios from 'axios'
import CityHeader from './components/Header'
import CitySearch from './components/Search'
import CityList from './components/List'
import CityAlphabet from './components/Alphabet'
export default {
name:'City',
components: {
CityHeader,
CitySearch,
CityList,
CityAlphabet
},
data () {
return {
cities: {},
hotCities: [],
letter: ''
}
},
methods: {
getCityInfo () {
axios.get('/api/city.json')
.then(this.handleGetCityInfoSucc)
},
handleGetCityInfoSucc (res) {
res = res.data
if(res.ret && res.data){
const data = res.data
this.cities = data.cities
this.hotCities = data.hotCities
}
},
handleLetterChange (letter) {
this.letter = letter;
}
},
mounted () {
this.getCityInfo()
}
}
</script>
<style lang="stylus" scoped>
</style>
List.vue(在這裡使用props接受父元件傳來的letter值,並使用watch來監聽letter值的變化,然後讓內容跟隨右側字母的變化):
<template>
<div class="list" ref='wrapper'>
<div>
<div class="area">
<div class="title border-topbottom">當前城市</div>
<div class="button-list">
<div class="button-wrapper">
<div class="button">成都</div>
</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">熱門城市</div>
<div class="button-list">
<div class="button-wrapper" v-for="item of hot" :key="item.id">
<div class="button">{{item.name}}</div>
</div>
</div>
</div>
<div
class="area"
v-for="(item,key) of cities"
:key="key"
:ref="key"
>
<div class="title border-topbottom">{{key}}</div>
<div class="item-list">
<div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">{{innerItem.name}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
name:'CityList',
props: {
hot: Array,
cities: Object,
letter: String
},
mounted () {
this.scroll = new BScroll(this.$refs.wrapper)
},
watch: {
letter () {
if (this.letter) {
const element = this.$refs[this.letter][0]
this.scroll.scrollToElement(element)
}
}
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.border-topbottom
&:before
border-color #ccc
&:after
border-color #ccc
.border-bottom
&:before
border-color #ccc
.list
overflow hidden
position absolute
top 1.58rem
left 0
right 0
bottom 0
.title
line-height .54rem
background #eee
padding-left .2rem
color #666
font-size .26rem
.button-list
padding .1rem .6rem .1rem .1rem
overflow hidden
.button-wrapper
float left
width 33.33%
.button
margin .1rem
padding .1rem 0
text-align center
border .02rem solid #ccc
border-radius .06rem
.item-list
.item
line-height .76rem
padding-left .2rem
</style>
- 在左側字母表中做上下拖拽的時候,城市列表內容也跟著變化
主要是監聽拖拽時間,在li增加了三個事件,分別是touchstart、touchmove、touchend來監聽拖拽事件,同時在li上使用ref,在data定義了一個touchStatus來儲存是否在拖拽的狀態,並使用計算屬性把cities中的字母取出來放在陣列letters中,最後計算拖拽到那個字母:
handleTouchStart () {
this.touchStatus = true
},
handleTouchMove (e) {
if(this.touchStatus){
const startY = this.$refs['A'][0].offsetTop
const touchY = e.touches[0].clientY -79
const index = Math.floor((touchY-startY) / 20)
if(index >= 0 && index < this.letters.length) {
this.$emit('change',this.letters[index])
}
}
},
handleTouchEnd () {
this.touchStatus = false
}
Alphabet.vue:
<template>
<ul class="list">
<li
class="item"
v-for="item of letters"
:key="item"
:ref="item"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@click="handleLetterClick"
>
{{item}}
</li>
</ul>
</template>
<script>
export default {
name:'CityAlphabet',
props: {
cities: Object
},
data () {
return {
touchStatus: false
}
},
computed: {
letters () {
const letters = []
for( let i in this.cities) {
letters.push(i)
}
return letters
}
},
methods: {
handleLetterClick (e) {
this.$emit('change',e.target.innerText)
},
handleTouchStart () {
this.touchStatus = true
},
handleTouchMove (e) {
if(this.touchStatus){
const startY = this.$refs['A'][0].offsetTop
const touchY = e.touches[0].clientY -79
const index = Math.floor((touchY-startY) / 20)
if(index >= 0 && index < this.letters.length) {
this.$emit('change',this.letters[index])
}
}
},
handleTouchEnd () {
this.touchStatus = false
}
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.list
display flex
flex-direction column
justify-content center
position absolute
top 1.58rem
right 0
bottom 0
width .4rem
.item
line-height .4rem
text-align center
color $bgColor
</style>
列表切換效能優化
- 把A字母離頂部的高度使用updated鉤子函式來計算,不用每次執行handleTouchMove函式的時候都計算
- 函式節流(定義了一個timer,如果正在執行拖拽操作,讓它延遲16毫秒來執行,如果在16毫秒之間又執行了手指滑動操作,它就會把上次要做的操作清楚掉,重新執行這次要做的事情,從而減少handleTouchMove函式的執行頻率)
Alphabet.vue:
<template>
<ul class="list">
<li
class="item"
v-for="item of letters"
:key="item"
:ref="item"
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
@click="handleLetterClick"
>
{{item}}
</li>
</ul>
</template>
<script>
export default {
name:'CityAlphabet',
props: {
cities: Object
},
data () {
return {
touchStatus: false,
startY: 0,
timer: ''
}
},
computed: {
letters () {
const letters = []
for( let i in this.cities) {
letters.push(i)
}
return letters
}
},
updated () {
this.startY = this.$refs['A'][0].offsetTop
},
methods: {
handleLetterClick (e) {
this.$emit('change',e.target.innerText)
},
handleTouchStart () {
this.touchStatus = true
},
handleTouchMove (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])
}
}, 16)
}
},
handleTouchEnd () {
this.touchStatus = false
}
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.list
display flex
flex-direction column
justify-content center
position absolute
top 1.58rem
right 0
bottom 0
width .4rem
.item
line-height .4rem
text-align center
color $bgColor
</style>
最後git提交程式碼到GitHub上
搜尋功能實現
- City.vue傳值到Search.vue子元件,通過watch監聽使用者輸入,從而修改輸入框下面顯示的陣列
- 使用v-show判斷使用者是否輸入了資料,從而實現查詢到的陣列內容區域的顯示隱藏
- 使用v-show判斷使用者輸入的值是否有對應的值,來實現查詢到的陣列資料的顯示和沒有查到內容時的提示
- 還使用了函式節流,讓使用者在輸入後100毫秒再執行,如果使用者在100毫秒之間再輸入就會把上次的操作清楚掉,增強效能
Search.vue:
<template>
<div>
<div class="search">
<input v-model="keyword" class="search-input" type="text" placeholder="輸入城市名或拼音">
</div>
<div
class="search-content"
ref="search"
v-show="keyword"
>
<ul>
<li
class="search-item border-bottom"
v-for="item of list"
:key="item.id"
>
{{item.name}}
</li>
<li class="search-item border-bottom" v-show="hasNoData">
沒有找到匹配資料
</li>
</ul>
</div>
</div>
</template>
<script>
import Bscroll from 'better-scroll'
export default {
name:'CitySearch',
props: {
cities: Object
},
data () {
return {
keyword: '',
list: [],
timer: null
}
},
computed: {
hasNoData () {
return !this.list.length
}
},
watch: {
keyword () {
if (this.timer) {
clearTimeout(this.timer)
}
if (!this.keyword) {
this.list = []
return
}
this.timer = setTimeout(() => {
const result = []
for (let i in this.cities) {
this.cities[i].forEach((value) => {
if (value.spell.indexOf(this.keyword) > -1 ||
value.name.indexOf(this.keyword) > -1){
result.push(value)
}
})
}
this.list = result
},100)
}
},
mounted () {
this.scroll = new Bscroll(this.$refs.search)
}
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.search
height .72rem
padding 0 .1rem
background $bgColor
.search-input
box-sizing border-box
width 100%
height .62rem
padding 0 .1rem
line-height .62rem
text-align center
border-radius .06rem
color #666666
.search-content
z-index 1
position absolute
overflow hidden
top 1.58rem
left 0
right 0
bottom 0
background #eee
.search-item
line-height .62rem
padding-left .2rem
background #ffffff
color #666
</style>
使用Vuex實現資料共享
- 首先建立一個city-vuex的分支,這裡需要實現的功能是點選城市列表頁面中城市,首頁的城市會發生改變
- vuex資料框架是vue大型專案的資料傳輸使用的框架
重要: vuex,我們可以想象成一個倉庫,其中有State、Actions、Mutations;State是所有的公用資料都存在State中,組建需要使用公用的資料,直接去呼叫State就可以了;Actions是我們可以把需要實現非同步操作或者是一些比較複雜的同步操作(批量的同步操作)存在Actions中;Mutations是對State同步的修改操作;元件中怎麼使用?(1.元件先去呼叫Actions,緊接著Actions去呼叫Mutations,然後Motations去修改State中的資料;2.元件直接呼叫Motations去修改State中的資料)
- 在專案中使用,先安裝這個vuex
npm install vuex --save
然後在專案中建立這個"倉庫區域",我們把它叫做store,並建立index.js檔案,最後在main.js中引入這個檔案
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { city: '成都' }, mutations: { changeCity (state, city) { state.city = city } } })
在元件中的使用(在List.vue和Search.vue中使用到了,實現效果就是在點選城市列表頁的時候,通過呼叫mutations中的方法來改變State中的資料),對對應的地方繫結handleCityClick()方法並且在methods中定義這個方法
<div class="button-wrapper" v-for="item of hot" :key="item.id" @click="handleCityClick(item.name)" > <div class="button">{{item.name}}</div> </div>
<div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id" @click="handleCityClick(innerItem.name)" > {{innerItem.name}} </div>
handleCityClick (city) { this.$store.commit('changeCity', city) this.$router.push('/') }
這個呼叫mutations中的方法使用的是commit,可以從上面的圖看到;這樣最簡單的使用vuex就完成了
Vuex的高階使用及localStorage
- 上面實現的功能會有問題,就是每次重啟應用的時候,選擇的城市會重置為State中預設的城市名,為了解決這個問題,在這裡就引入了h5中的localStorage本地快取來實現
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) export default new Vuex.Store({ state: { city: localStorage.city || '成都' }, mutations: { changeCity (state, city) { state.city = city localStorage.city = city } } })
改進版(防止使用者沒有開啟本地快取功能或者是隱身模式):
import Vue from 'vue' import Vuex from 'vuex' Vue.use(Vuex) let defaultCity = '上海' try { if (localStorage.city) { defaultCity = localStorage.city } } catch (e) {} export default new Vuex.Store({ state: { city: defaultCity }, mutations: { changeCity (state, city) { state.city = city try { localStorage.city = city } catch (e) {} } } })
最終版(拆分成了state.js和mutations.js):
// state.js let defaultCity = '上海' try { if (localStorage.city) { defaultCity = localStorage.city } } catch (e) {} export default { city: defaultCity }
// mutations.js export default{ changeCity (state, city) { state.city = city try { localStorage.city = city } catch (e) {} } }
// index.js import Vue from 'vue' import Vuex from 'vuex' import state from './state' import mutations from './mutations' Vue.use(Vuex) export default new Vuex.Store({ state, mutations })
- 還有個樣式問題,就是選擇四個字的城市時,頂部會被撐開,直接修改home/components下的Header.vue中的.header-right樣式
.header-right min-width 1.04rem //修改的 padding 0 .1rem //新增的 float right text-align center color #ffffff
- 優化程式碼:使用vuex提供的mapState來簡化程式碼
// home下的Header.vue的js部分 <script> import { mapState } from 'vuex' //新增 export default { name: 'HomeHeader', computed: { //計算屬性新增的 ...mapState(['city']) } } </script>
使用:
<router-link to='/city'> <div class="header-right"> {{this.city}} //從this.$store.state.city精簡成的this.city <i class="iconfont arrow-icon"></i> </div> </router-link>
city/components中List.vue:
<template> <div class="list" ref='wrapper'> <div> <div class="area"> <div class="title border-topbottom">當前城市</div> <div class="button-list"> <div class="button-wrapper"> <div class="button">{{this.currentCity}}</div> </div> </div> </div> <div class="area"> <div class="title border-topbottom">熱門城市</div> <div class="button-list"> <div class="button-wrapper" v-for="item of hot" :key="item.id" @click="handleCityClick(item.name)" > <div class="button">{{item.name}}</div> </div> </div> </div> <div class="area" v-for="(item,key) of cities" :key="key" :ref="key" > <div class="title border-topbottom">{{key}}</div> <div class="item-list"> <div class="item border-bottom" v-for="innerItem of item" :key="innerItem.id" @click="handleCityClick(innerItem.name)" > {{innerItem.name}} </div> </div> </div> </div> </div> </template> <script> import BScroll from 'better-scroll' import { mapState, mapMutations } from 'vuex' export default { name:'CityList', computed: { ...mapState({ currentCity: 'city' //這裡使用的物件,就是把state中的city對映到這個元件的計算屬性中這個值 }) }, props: { hot: Array, cities: Object, letter: String }, methods: { handleCityClick (city) { // this.$store.commit('changeCity', city) 使用mutations的方法之前的呼叫 this.changeCity(city) this.$router.push('/') }, ...mapMutations(['changeCity']) //使用mapMutations來簡化mutations中的方法呼叫 }, mounted () { this.scroll = new BScroll(this.$refs.wrapper) }, watch: { letter () { if (this.letter) { const element = this.$refs[this.letter][0] this.scroll.scrollToElement(element) } } } } </script> <style lang="stylus" scoped> @import '~styles/varibles.styl' .border-topbottom &:before border-color #ccc &:after border-color #ccc .border-bottom &:before border-color #ccc .list overflow hidden position absolute top 1.58rem left 0 right 0 bottom 0 .title line-height .54rem background #eee padding-left .2rem color #666 font-size .26rem .button-list padding .1rem .6rem .1rem .1rem overflow hidden .button-wrapper float left width 33.33% .button margin .1rem padding .1rem 0 text-align center border .02rem solid #ccc border-radius .06rem .item-list .item line-height .76rem padding-left .2rem </style>
其他的元件一樣使用這種方法來精簡程式碼(這裡我把之前建立的沒有用的資料夾List和元件也刪除了)
- 另外講一下vuex中的getter和module,這 getter就是store 的計算屬性,通過把State中的資料計算後,元件就可以之間呼叫getter使用;module是當由於使用單一狀態樹,應用的所有狀態會集中到一個比較大的物件。當應用變得非常複雜時,store 物件就有可能變得相當臃腫。 為了解決以上問題,Vuex 允許我們將 store 分割成模組(module)。每個模組擁有自己的 state、mutation、action、getter、甚至是巢狀子模組——從上至下進行同樣方式的分割
使用keep-alive優化頁面效能
- 首先建立在GitHub一個city-keepalive的分支
- 在App.vue中使用<keep-alive>標籤
<template> <div id="app"> <keep-alive> <router-view/> <!-- 顯示的是當前路由地址所對應的內容 --> </keep-alive> </div> </template> <script> export default { name: 'App' } </script> <style></style>
作用是路由的內容載入過一次之後,就把路由中的內容放到記憶體之中,下次再重新進這個路由時,不需要重新渲染元件
- 還有邏輯問題,就是在重新選擇城市後在Home.vue中需要重新進行Ajax請求,在Home.vue中引入vuex
import { mapState } from 'vuex'
修改getHomeinfo方法,在請求的時候帶上state中的city
getHomeinfo () { axios.get('/api/index.json?city=' + this.city) .then(this.getHomeinfoSucc) },
這樣修改後,重新選擇城市也不會重新進行Ajax請求,這裡就需要用到activated(使用keep-alive才有的鉤子函式)使用mounted和activated這兩個鉤子函式來實現
mounted () { this.lastCity = this.city this.getHomeinfo() }, activated () { if (this.lastCity !== this.city) { this.lastCity = this.city this.getHomeinfo() } }
最後把代表提交到GitHub上