上篇,我们实现了城市选择页面的搜索逻辑。本篇,我们来实现,首页与城市选择页面之间的数据共享。
首先创建分支 city-vuex, pull 下面,在新分支上写代码。
vuex 实是一个数据框架,而且它也是官方推荐的方案。在使用Vue 进行开发时,Vue 主要承担视图层的内容,当我们涉及到大量数据进行传递的时候,就需要数据框架。Vue 之中的这个数据框架,就是Vuex。
Vuex 是什么呢?
当我们项目中,各个页面,多个组件之间进行复杂的数据传值很困难的时候,如果把这些公用的数据放到公共的存储空间存储,某一个组件改变了其中某一个数据,其他组件能够感知到。Vuex 的设计理念就是这样。
官网给出了下图。虚线框起来的就是公用数据存储区域,这一区域有三部分组成:State 是所有的公用数据存放的地方,组件想要使用公用数据,直接去 State 中调用就可以;当组件想要改变公用数据时,组件得先去调用Actions 做一些异步处理或者批量同步操作, Actions 会去调用 Mutations , Mutations 中放置的是一个个同步的对 State 的修改。当然,组件也可以跳过Actions 直接通过Mutations 对数据进行修改。
注意,当组件调用Actions 的时候,是通过Dispatch 方法来操作的。组件或者Actions 调用Mutations 时,是通过Commit 方法操作的。
开始使用vuex.
首先,在项目里安装vuex.
npm install vuex --save
然后去引入vuex
我们可以在 项目的 main.js 中引如 vuex
但因为可能后面代码会比较复杂,所以,我们在src 目录下建一个目录 store, 然后在这个目录下建一个文件 index.js
下面是index.js 内容
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '北京'
}
})
然后,我们去main.js 里引入 ,如下。
import Vue from 'vue'
import App from './App'
import router from './router'
import fastClick from 'fastclick'
import VueAwesomeSwiper from 'vue-awesome-swiper'
import store from './store'
import 'swiper/dist/css/swiper.css'
import 'styles/reset.css'
import 'styles/border.css'
import 'styles/iconfont.css'
Vue.config.productionTip = false
fastClick.attach(document.body)
Vue.use(VueAwesomeSwiper)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
好啦。
原本,首页上的城市,是ajax 返回的信息,现在我们改为由前端确定。先把以前的相关代码删掉。在home 目录下的Home.vue 与 home / components 的Header.vue。
首先是Header.vue 【因为在根Vue示例中,申明了store,因此各个子组件中都可以使用this.$store 访问 store】
<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>
Home.vue 就不列出了。
同时,城市选择页面进来的时候,当前城市,也应该是state 中city 的值。
因此,city/components 下的 List.vue 也要做改动。
然后,我们希望在城市选择页面,点击一个热门城市,state 中的city 会改变。
先看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-wraper">
<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-wraper"
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="innerItem.name"
>
{{innerItem.name}}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Bscroll from 'better-scroll'
export default {
name: 'CityList',
props: {
cities: Object,
hot: Array,
letter: String
},
methods: {
handleCityClick (city) {
this.$store.dispatch('changeCity', city)
}
},
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>
然后是store / index.js 代码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '上海'
},
actions: {
changeCity (ctx, city) {
ctx.commit('changeCity',city)
}
},
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
上面由于,没有异步没有批量修改 数据,可以直接跳过 Actions 直接 使用mutation。
如下
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-wraper">
<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-wraper"
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="innerItem.name"
>
{{innerItem.name}}
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import Bscroll from 'better-scroll'
export default {
name: 'CityList',
props: {
cities: Object,
hot: Array,
letter: String
},
methods: {
handleCityClick (city) {
this.$store.commit('changeCity', city)
// this.$store.dispatch('changeCity', city)
}
},
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>
store / index.js 代码
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '上海'
},
// actions: {
// changeCity (ctx, city) {
// ctx.commit('changeCity',city)
// }
// },
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
好啦,最终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-wraper">
<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-wraper"
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'
export default {
name: 'CityList',
props: {
cities: Object,
hot: Array,
letter: String
},
methods: {
handleCityClick (city) {
this.$store.dispatch('changeCity', city)
this.$router.push('/')
}
},
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>
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"
@click="handleCityClick(item.name)"
>
{{item.name}}</li>
<li
class="search-item border-bottom"
v-show="hasList"
>没有找到匹配数据</li>
</ul>
</div>
</div>
</template>
<script>
import Bscroll from 'better-scroll'
export default {
name: 'CitySearch',
props: {
cities: Object
},
data () {
return {
keyword: '',
list: [],
timmer: null
}
},
computed: {
hasList () {
return !this.list.length
}
},
methods: {
handleCityClick (city) {
this.$store.dispatch('changeCity', city)
this.$router.push('/')
}
},
watch: {
keyword () {
if (!this.keyword) {
this.list = []
return
}
if (this.timmer) {
setTimeout(this.timmer)
}
this.timmer = 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>
Done!