目标:使用Vuex实现首页和城市选择页面的数据共享
功能: 当我点击城市选择页面上的某个城市的时候,首页的右上角呈现的就是这个城市。相当于,我需要将城市选择页面的数据传递给首页,实现两个页面的数据共享。
我们会发现,City.vue与Home.vue两个组件之间并没有一个共同的父组件可以作为桥梁去实现两个组件之间的数据传递。我们可以使用总线的方式,但是依然比较麻烦,Vue官方提供了一个数据框架vuex。 在Vue相关项目的开发中,Vue只能承担视图层的主要能容,当涉及到大量数据需要传递的时候,往往都需要一个数据 框架进行辅助。Vuex就是Vue的一个数据辅助工具。
学习Vuex官方文档 官网的流程图一定要看懂!!!
简单理解: 当我们的项目中各个页面或者多个组件之间进行复杂的数据传值很困难的时候,将这些公用的数据放在一个公共的存储空间去存储,某一个组件改变了这个公共存储空间的数据时,其他的组件就能感知到。
Vuex的安装与初步使用
npm install vuex --save
- 文件创建:
src/store/index.js
- 使用:
在src/store/index.js
中,使用这个插件:
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
注意:我们要导出的不是Vuex,而是通过Vuex创建的一个仓库:
export default new Vuex.Store ({
state: {
city: '上海'
}
})
在仓库中有个state,它里面存放的是一些公用的数据,对于首页和城市选择页面来说,我们公用的页面就是城市这个数据, 设置这个数据默认值为上海。
我们在main.js当中import store from './store/index'
后,在创建根Vue实例的时候将这个store传入进去:
new Vue({
el: '#app',
router,
store,
render: h => h(App)
})
实际上,我们创建的这个store就相当于官网流程图中的绿色虚线部分,然后创建了一个state区域,并存储了city数据, 接下来就是让组件来使用这些公用的数据。
首先, 首页中的header部分使用到这个数据了。我们之前的操作是将这个数据通过属性的形式传递进去的:
<home-header :city="city"></home-header>
而这个数据是由ajax从后端获取到的,现在不需要传递这个数据了,希望数据是前端存储的,不需要从后端告诉我们用户当前在哪个城市了。我们把这个页面中data当中的city去掉,后面有关于city的操作也都去掉,还有在mock目录下的index.json中的city也去掉,此时<home-header></home-header>
就没有接收到任何数据了,首页中右侧就不会显示当前城市了。
现在我们想把公共数据中的“上海”显示出来,要怎么做?
在首页的Header.vue中,props中的city原本是从外部接收来的,现在不需要使用了,就可以直接把props删掉,在页面渲染时this.city
就需要改成this.$store
, 这里的$store
指的是我们刚刚在src/store/index.js
中创建的Vuex.Store
。 为什么每一个子组件都能使用这个$store
? 是因为,在,main.js中,创建根实例的时候,把store传递进去了,紧接着Vuex创建的这个store就会被派发到每一个子组件当中去,所以每一个子组件中都可以使用this.$store
获取到这个store。 在store中有一个公用数据state,这个公用数据中有一个city,所以在这里原本的this.city
就变成了this.$store.state.city
:
<div class="header-right">
{{this.$store.state.city}}
<span class="iconfont arrow-icon"></span>
</div>
此时,“上海”就被显示在首页上了。
在城市选择页面中,当前城市的位置显示的也应该是"上海"这个城市,直接把当前城市中的“上海”替换成this.$store.state.city
即可。
使用vuex实现公共数据city的更新
当点击热门城市的时候,公用数据里面的city数据会跟着发生变化。对应官网上的流程图: 我希望改变state的时候,首先需要调用Actions,然后再去调用Mutations。
在List.vue组件中,给每一个热门城市的内容都绑定一个点击事件:
<div class="area">
<div class="title border-topbottom">热门城市</div>
<div class="button-list">
<div
class="button-wrapper"
v-for="item of hotCities"
:key="item.id"
@click="handleCityClick(item.name)"
>
<div class="button">{{item.name}}</div>
</div>
</div>
</div>
methods: {
handleCityClick (city) {
alert(city)
this.$store.dispatch('changeCity', city)
}
}
根据流程图显示,我需要在这个组件中使用dispatch方法调用Vuex中的actions方法。this.$store.dispatch('changeCity', city)
表示,当city数据发生变化的时候,使用dispatch派发一个名字叫作changeCity的action,第二个参数是city数据。但是,我们在创建store的时候,里面只有一个state没有actions,所以需要添加一个名为changeCity的action:
export default new Vuex.Store ({
state: {
city: '上海'
},
actions: {
changeCity (ctx, city) {
ctx.commit('changeCity', city)
}
}
})
changeCity
是一个方法,它会接收两个参数,第一个参数是上下文ctx,第二个参数就是从List.vue组件传递过来的数据city。此时,actions当中已经接收到我们传递过来的城市,它还需要调用mutations
去改变这个公用的数据,所以接下来就是创建mutations:
export default new Vuex.Store ({
state: {
city: '上海'
},
actions: {
changeCity (ctx, city) {
ctx.commit('changeCity', city)
}
},
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
在每一个mutation当中也会对应两个参数,第一个参数是state,第二个参数是外部传过来的city数据, 想要在action执行的时候,需要借用ctx使用commit方法去调用changeCity这个mutation,传过去的内容就是city,此时在changeCity这个mutation当中让state中的city变成传入的这个city就完成了公共数据的更新。 测试: 在页面上点击热门城市中的“北京”时,当前位置会变成“北京”,首页的header部分也会变成“北京”。
在上面的过程中,改变state的过程中没有异步操作,而且这个操作也是很简单的,不是批量的数据操作,这种情况下,组件没有必要调用actions去做转发,组件可以直接调用mutation,可以将actions部分先删除掉,做一下代码的修改:
export default new Vuex.Store ({
state: {
city: '上海'
},
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
相应的,在List.vue组件中就不需要使用dispatch调用action,而是使用commit直接调用mutation,所以:
methods: {
handleCityClick (city) {
this.$store.commit('changeCity', city)
}
}
其他的几个小功能的实现
- 点击各个字母中对应的城市时可能实现数据切换
将List.vue中热门城市区域的点击事件复制一份直接放到第三个区域中:
<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>
- 在城市搜索的时候,希望点击搜索出来的城市也能够实现公共数据的改变
将List.vue中的handleCityClick方法复制一份到Search.vue组件中,并在模板中绑定这个点击事件:
<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="hasNoData">没有找到匹配数据</li>
</ul>
至此,两个页面之间的通信就完成了。
- 当我在城市选择页面中点击某一个具体的城市时,不但让当前城市变成这个城市,还希望让它立即返回到首页。 — 使用 路由
Vue.js官网 —> 生态系统 —> Vue Router —> 编程式导航
在网页上做页面跳转有两种方式: 一种是通过a标签的形式做跳转,另一种方式是js的window.location.href的形式做页面的跳转。在vue中可以通过标签的形式做页面的跳转,同时也可以做js形式的页面跳转,这种形式的页面跳转会更复杂一些,在Vue Router中使用的是编程式导航的形式,在编程式导航中提供了一个push方法,可以帮助我们实现页面的跳转。
当我点击某个城市的时候,首先改变了公共数据中的城市的内容,即this.$store.commit('changeCity', city)
,然后就是要实现页面的跳转:this.$router.push('/')
。
methods: {
handleCityClick (city) {
this.$store.commit('changeCity', city)
this.$router.push('/')
}
}
此时,两个页面之间的联动就完成了。