之前做了首页和城市选择页面,现在需要点击城市选择页面,选择城市,首页的城市跟着一起变,这就需要首页和城市选择页面有一些关联,需要用到Vuex。
Vuex是Vue官方推荐我们使用的数据框架,在Vue大型项目开发中Vue只能承担视图层的主要内容,涉及到大量数据之间传递时,需要一个数据框架进行辅助,就是Vuex
上图的大概意思是:
State是公用数据,组件可直接调用;组件改数据必须先调用Actions做一些异步处理或批量操作,然后Actions再去调用Mutations,只有通过Mutations,最后才可改变State的值,这样当数据发生变化时,组件也会发生变化。
上面这种情况也不是绝对的,有时也可略过Actions,让组件直接去调用Mutations,来修改State的公用数据
mutation 必须同步执行; Action 就不受约束!我们可以在 action 内部执行异步操作
文章目录
安装Vuex
Vuex官方文档: https://vuex.vuejs.org/zh/installation.html
npm安装
npm install vuex --save
由于Vuex数据比较复杂就不直接在main.js中引入了
在src文件夹下新建 store文件夹 > index.js
在一个模块化的打包系统中,您必须显式地通过 Vue.use()
来安装 Vuex:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
store文件夹 > index.js
store文件夹 > index.js 引入到main.js
现在项目里就已经使用了Vuex
现在已经创建好了Vuex(红框部分),就是main.js引入的store
之前在state公用数据里面存了一个city,下一步需要组件来使用这个数据,根据之前的页面选择首页的header.vue组件
来使用这个数据
Home.vue
现在不需要用axios从后端传数据,已经在前端存储过了
<template>
<div>
<home-header></home-header>
<home-swiper :list="swiperList"></home-swiper>
<home-icons :list="iconList"></home-icons>
<home-recommend :list="recommendList"></home-recommend>
<home-weekend :list="weekendList"></home-weekend>
</div>
</template>
<script>
// 局部组件需要插入到components中,由于键和值都是一样的,所以写成HomeHeader
import HomeHeader from './components/Header'
import HomeSwiper from './components/Swiper'
import HomeIcons from './components/Icons'
import HomeRecommend from './components/Recommend'
import HomeWeekend from './components/Weekend'
import axios from 'axios'
export default {
name: 'home',
data () {
return {
swiperList: [],
iconList: [],
recommendList: [],
weekendList: []
}
},
components: {
HomeHeader,
HomeSwiper,
HomeIcons,
HomeRecommend,
HomeWeekend
},
methods: {
getHomeInfo () {
axios({
url: '/static/mock/index.json',
method: 'get'
}).then(this.getHomeInfoSucc)
},
getHomeInfoSucc (res) {
console.log(res)
res = res.data
if (res.ret && res.data) {
const data = res.data
this.swiperList = data.swiperList
this.iconList = data.iconList
this.recommendList = data.recommendList
this.weekendList = data.weekendList
}
}
},
// 钩子函数mounted
mounted () {
this.getHomeInfo()
}
}
</script>
<style></style>
static > mock > index.json也删除掉城市信息
返回首页,现在城市信息就是空的
公用数据显示在header.vue和 list.vue组件
<template>
<div class="header">
<div class="header-left">
<div class="iconfont back-icon"></div>
</div>
<div class="header-input">
<span class="iconfont"></span>
输入城市/景点/游玩主题
</div>
<router-link to="/city">
<div class="header-right">
{{this.$store.state.city}}
<span class="iconfont arrow-icon"></span>
</div>
</router-link>
</div>
</template>
<script>
export default {
name: 'HomeHeader',
}
</script>
<style lang="scss" scoped>
// styles指的是webpack.base.conf.js中的 src/assets/styles路径的简写
@import '~styles/varibles.scss';
.header {
display: flex;
line-height: $HeaderHeight;
background-color: $bgColor;
color: #fff;
.header-left {
width: 0.64rem;
float: left;
.back-icon {
font-size: 0.4rem;
text-align: center;
}
}
.header-input {
flex: 1;
background: #fff;
border-radius: 0.1rem;
padding-left: 0.2rem;
margin-top: 0.12rem;
margin-left: 0.2rem;
height: 0.64rem;
line-height: 0.64rem;
color: #666;
}
.header-right {
width: 1.24rem;
float: right;
text-align: center;
color: #fff;
.arrow-icon {
margin-left: -0.04rem;
font-size: 0.24rem;
}
}
}
</style>
city文件夹下得list.vue也得改一下
实现点击逻辑功能
点击切换城市就是改变state
,在组件里改变state需要按下图顺序
现在由于没有异步操作或批量操作,逻辑也没有很复杂,可略过Actions,直接去调用Mutations
给每个内容绑定一个click事件,调用方法时把item.name传进来,把方法写在methods中
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.$store.state.city }}</div>
</div>
</div>
</div>
<div class="area">
<div class="title border-topbottom">热门城市</div>
<div class="button-list">
<div
class="button-wrapper"
v-for="item in hot"
:key="item.id"
@click="handleCityClick(item.name)"
>
<div class="button">{{ item.name }}</div>
</div>
</div>
</div>
<div class="area" v-for="(item, key) in cities" :key="key" :ref="key">
<div class="title border-topbottom">{{ key }}</div>
<div class="item-list">
<div
v-for="innerItem in item"
:key="innerItem.id"
class="item border-bottom"
@click="handleCityClick(innerItem.name)"
>
{{ 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
},
methods: {
handleCityClick(city) {
this.$store.commit('changeCity', city)
this.$router.push('/')
}
},
// 监听letter
watch: {
letter() {
// console.log(this.letter)
// better-scroll提供给我们这样一个接口,如果letter不为空时,调用this.scroll.scrollToElement()方法;让better-scroll的滚动区域自动滚到某个元素上
if (this.letter) {
const element = this.$refs[this.letter][0]
this.scroll.scrollToElement(element)
}
}
},
updated() {
this.scroll = new BScroll(this.$refs.wrapper)
}
}
</script>
<style lang="scss" scoped>
@import '~styles/varibles.scss';
// 1像素边框问题
.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 {
padding: 0.1rem 0.2rem;
line-height: 0.44rem;
background: #eee;
}
.button-list {
overflow: hidden;
padding: 0.1rem 0.6rem 0.1rem 0.1rem;
.button-wrapper {
float: left;
width: 33.33%;
.button {
margin: 0.1rem;
padding: 0.14rem 0;
text-align: center;
border: 0.02rem solid #ccc;
border-radius: 0.1rem;
}
}
}
.item-list {
.item {
line-height: 0.76rem;
background: #fff;
padding-left: 0.2rem;
}
}
}
</style>
store > index.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
}
}
})
Search.vue中也要实现点击逻辑
<template>
<div>
<div class="search">
<input
class="search-input"
type="text"
placeholder="输入城市名或拼音"
v-model="keyWord"
/>
</div>
<!-- 有值时才显示 -->
<div class="search-content" ref="search" v-show="keyWord">
<ul>
<li
class="search-item border-bottom"
v-for="item in list"
:key="item.id"
@click="handleCityClick(item.name)"
>
{{ 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
}
},
// 监听keyword
// 这里用到函数节流,因为这里用的频率比较高,节约性能
watch: {
keyWord () {
if (this.timer) {
clearTimeout(this.timer)
}
// 如果input框没有输入内容,那就设置this.list为空数组
if (!this.keyWord) {
this.list = []
return
}
this.timer = setTimeout(() => {
const result = []
// 循环从父组件接收cities里面的内容
for (let i in this.cities) {
// 把cities里面的A、B、C等等键值对里面的值再给遍历一遍
this.cities[i].forEach(value => {
// 如果从spell,name中能搜索到这个关键词,就把这一项添加到result中
if (
value.spell.indexOf(this.keyWord) > -1 ||
value.name.indexOf(this.keyWord) > -1
) {
result.push(value)
}
})
}
// 把result结果赋值给list数组,在上面的li中循环list数组
this.list = result
}, 100)
}
},
methods: {
handleCityClick (city) {
this.$store.commit('changeCity', city)
this.$router.push('/')
}
},
// 列表解决不滑动
updated() {
this.scroll = new BScroll(this.$refs.search)
}
}
</script>
<style lang="scss" scoped>
@import '~styles/varibles.scss';
.search {
background: $bgColor;
height: 0.72rem;
padding: 0.1rem;
padding: 0 0.2rem;
.search-input {
width: 100%;
height: 0.62rem;
line-height: 0.62rem;
text-align: center;
background: #fff;
border-radius: 0.06rem;
color: #666;
}
}
.search-content {
overflow: hidden;
position: absolute;
top: 1.58rem;
left: 0;
right: 0;
bottom: 0;
background: #eee;
z-index: 1;
.search-item {
line-height: 0.62rem;
padding-left: 0.2rem;
color: #666;
background: #fff;
}
}
</style>