Vue实战项目开发--城市列表页面
城市选择页面路由配置创建一个city-router的分支,用于城市列表的功能实现路由配置,修改router/index.js的内容import Vue from 'vue'import Router from 'vue-router'Vue.use(Router)export default new Router({routes: [{path...
城市选择页面路由配置
- 创建一个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
在文档可以看到better-scroll的使用better-scroll的GitHub地址
- 修改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上
更多推荐
所有评论(0)