转载请注明出处,未经同意,不可修改文章内容。
🔥🔥🔥“前端一万小时”两大明星专栏——“从零基础到轻松就业”、“前端面试刷题”,已于本月大改版,合二为一,干货满满,欢迎点击公众号菜单栏各模块了解。
1 需求
在城市选择页点击任意城市名后,首页头部的城市名跟着发生改变;当选择了城市后,跳转回首页:
2 首页与城市选择页数据共享
需求分析:
点击城市选择页的“城市”后,首页头部的“城市”跟着变化,这就要求首页与城市选择页之间要进行通信,传递数据。且在这两个组件之间,没有一个公用的父级组件,它们之间无法通过一个父级组件进行数据的中转。
之前《深入理解 Vue 组件——⑤ 非父子组件间的传值》中提到过非父子组件间通信的两种方案: Vuex 和 发布订阅模式。
发布订阅模式我们已经学习过了,但它用在现在的“qdywxs-travel 项目”中依然比较麻烦。所以,这里我们采用 Vuex 的方案。
2.1 Vuex 是什么
Vuex 是 Vue 官方提供的一个数据框架。
在 Vue 的大型项目开发中,Vue 只能承担视图层的主要内容。当我们涉及到大量数据之间的传递时,往往需要一个数据框架——Vuex 来进行辅助。
在 Vue 官网的“生态系统”中,可以找到 Vuex:
在“Vuex 是什么”这个章节里,可以找到一张关于 Vuex 的图。只要弄明白这张图,那么我们也就学会使用 Vuex 了:
❓如何来理解这张图呢?
在一个项目中,如果多个页面(或多个组件)之间进行传值非常困难的时候,我们可能会想:如果可以把所有公共的数据,放在一个公共的空间来存储。当某一个组件改变了公共的数据后,其他的组件也能感知到。这样,不就能很方便的实现我们的需求了吗?
Vuex 的设计理念就是这样的:整个 Vuex 绿色虚线的部分,就是公用数据的存储区域,这个区域可以理解为一个仓库。
这个仓库由几个部分共同组成的:
- State 存放所有的公用数据,当组件想用某个公用的数据,直接调用 State;
- 如果某个组件想要改变公用数据,组件不能直接更改数据,必须走一个“流程”:
- 组件先调用 Actions;
- Actions 再去调用 Mutations;
- Mutations 再一个一个同步的对 State 进行修改。
如果是异步的操作,或者比较复杂的、批量的同步操作,放在 Actions 里。有时,我们也可以越过 Actions,让组件直接去调用 Mutations 修改 State 中的数据。
但,只有通过 Mutations,我们才能改变公用数据的值。
❗️注意:组件调用 Actions 时,是通过一个 Dispatch 方法;组件或 Actions 调用 Mutations 时,是通过一个 Commit 方法。
Vuex 的内容,其实就是这样一个单向数据改变流程。在使用 Vuex 时,我们还可以使用 Devtools 对代码进行调试(❗️上图建议保存,在后面的内容中,对照上图跟着文章来理解)。
2.2 使用 Vuex
要使用 Vuex,我们需要在项目中进行安装。
1️⃣打开终端,在项目目录下运行 npm install vuex --save :
2️⃣安装好 Vuex 后,我们对照图片内容来使用:
2️⃣-①:在 src 目录下 新建一个 store 文件夹,并在 store 文件夹中创建一个 index.js 文件;
import Vue from 'vue' // 2️⃣-②:首先引入 Vue;
import Vuex from 'vuex' // 2️⃣-③:再引入 Vuex;
Vue.use(Vuex) /*
2️⃣-④:通过 Vue.use 使用 Vuex(因为 Vuex 也是一个插件,Vue 之中用 Vue.use
来使用插件);
*/
export default new Vuex.Store({ /*
2️⃣-⑦:最后,导出一个通过 Vuex 创建的仓库 Store。
❗️创建仓库是通过 new Vuex.Store()
*/
state: { // 2️⃣-⑤:根据 Vuex 图来看,仓库中有一个 state 的内容,里面存放所有的公用数据;
city: '北京' /*
2️⃣-⑥:而对于首页和城市选择页来说,公用的数据就是 city,这里我们让默认城市为“北京”;
*/
}
})
2️⃣-⑧:打开 main.js ,在项目中引入 Store;
// The Vue build version to load with the `import` command
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
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' /*
2️⃣-⑨:引入当前目录下 store 中的 index.js(这里可以
省略 store后面的 /index.js,Vue 会自动去找);
*/
import 'styles/reset.css'
import 'styles/border.css'
import 'styles/iconfont.css'
import 'swiper/dist/css/swiper.css'
Vue.config.productionTip = false
fastClick.attach(document.body)
Vue.use(VueAwesomeSwiper)
/* eslint-disable no-new */
new Vue({
el: '#app',
router,
store, // 2️⃣-⑩:在创建根 Vue 实例时,把 store 传入进去。
components: { App },
template: '<App/>'
})
保存后,返回页面查看,控制台无报错,项目中的功能正常即正确使用了 Vuex:
❓组件如何使用公用数据呢?
答:“当组件想用某个公用的数据,直接调用 State”。而首先使用到公用数据的组件,是首页的 Header.vue 的“城市”。
3️⃣打开 pages 下 home 中的 Home.vue :
<template>
<div>
<home-header></home-header> <!-- 3️⃣-①:删除首页 Header 组件接收的数据 city
(因为我们将使用 State 中的公用数据,不再需要父组件传递
过来的数据了); -->
<!-- <home-header :city="city"></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>
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',
components: {
HomeHeader,
HomeSwiper,
HomeIcons,
HomeRecommend,
HomeWeekend
},
data () {
return {
// city: '', // 3️⃣-②:删除 data 中的数据 city;
swiperList: [],
iconList: [],
recommendList: [],
weekendList: []
}
},
methods: {
getHomeInfo () {
axios.get('/api/index.json')
.then(this.getHomeInfoSucc)
},
getHomeInfoSucc (res) {
res = res.data
if (res.ret && res.data) {
const data = res.data
// this.city = data.city // 3️⃣-③:删除后端请求到的数据 city;
this.swiperList = data.swiperList
this.iconList = data.iconList
this.recommendList = data.recommendList
this.weekendList = data.weekendList
}
}
},
mounted () {
this.getHomeInfo()
}
}
</script>
<style>
</style>
3️⃣-④:打开 mock 下的 index.json 删除 data 中的 city ;
{
"ret": true,
"data": {
// "city": "北京", // ❗️删除 data 中的 city。
"swiperList": [{
"id": "0001",
"imgUrl": "https://qdywxs.github.io/travel-images/swiperList01.jpg"
},{
"id": "0002",
"imgUrl": "https://qdywxs.github.io/travel-images/swiperList02.jpg"
},{
"id": "0003",
"imgUrl": "https://qdywxs.github.io/travel-images/swiperList03.jpg"
},{
"id": "0004",
"imgUrl": "https://qdywxs.github.io/travel-images/swiperList04.jpg"
}],
"iconList": [{
"id": "0001",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList01.png",
"desc": "景点门票"
}, {
"id": "0002",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList02.png",
"desc": "滑雪季"
}, {
"id": "0003",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList03.png",
"desc": "泡温泉"
}, {
"id": "0004",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList04.png",
"desc": "动植园"
}, {
"id": "0005",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList05.png",
"desc": "游乐园"
}, {
"id": "0006",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList06.png",
"desc": "必游榜单"
}, {
"id": "0007",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList07.png",
"desc": "演出"
}, {
"id": "0008",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList08.png",
"desc": "城市观光"
}, {
"id": "0009",
"imgUrl": "https://qdywxs.github.io/travel-images/iconList09.png",
"desc": "一日游"
}],
"recommendList": [{
"id": "0001",
"imgUrl": "https://qdywxs.github.io/travel-images/recommendList01.jpg",
"title": "故宫",
"desc": "东方宫殿建筑代表,世界宫殿建筑典范"
}, {
"id": "0002",
"imgUrl": "https://qdywxs.github.io/travel-images/recommendList02.jpg",
"title": "南山滑雪场",
"desc": "北京专业级滑雪圣地"
}, {
"id": "0003",
"imgUrl": "https://qdywxs.github.io/travel-images/recommendList03.jpg",
"title": "天安门广场",
"desc": "我爱北京天安门,天安门上太阳升"
}, {
"id": "0004",
"imgUrl": "https://qdywxs.github.io/travel-images/recommendList04.jpg",
"title": "水立方",
"desc": "中国的荣耀,阳光下的晶莹水滴"
}, {
"id": "0005",
"imgUrl": "https://qdywxs.github.io/travel-images/recommendList05.jpg",
"title": "温都水城养生馆",
"desc": "各种亚热带植物掩映其间,仿佛置身热带雨林"
}],
"weekendList": [{
"id": "0001",
"imgUrl": "https://qdywxs.github.io/travel-images/weekendList01.jpg",
"title": "北京温泉排行榜",
"desc": "细数北京温泉,温暖你的冬天"
}, {
"id": "0002",
"imgUrl": "https://qdywxs.github.io/travel-images/weekendList02.jpg",
"title": "北京必游TOP10",
"desc": "来北京必去的景点非这些地方莫属"
}, {
"id": "0003",
"imgUrl": "https://qdywxs.github.io/travel-images/weekendList03.jpg",
"title": "寻找北京的皇城范儿",
"desc": "数百年的宫廷庙宇,至今依旧威严霸气"
}, {
"id": "0004",
"imgUrl": "https://qdywxs.github.io/travel-images/weekendList04.jpg",
"title": "学生最爱的博物馆",
"desc": "周末干嘛?北京很多博物馆已经免费开放啦"
}, {
"id": "0005",
"imgUrl": "https://qdywxs.github.io/travel-images/weekendList05.jpg",
"title": "儿童剧场,孩子的乐园",
"desc": "带宝贝观看演出,近距离体验艺术的无穷魅力"
}]
}
}
3️⃣-⑤:打开 home 中 components 下的 Header.vue ;
<template>
<div class="header">
<div class="header-left">
<span class="iconfont back-icon"></span>
</div>
<div class="header-input">
<span class="iconfont"></span>
输入城市/景点/游玩主题
</div>
<router-link to="/city">
<div class="header-right">
{{this.$store.state.city}} <!-- 3️⃣-⑦:通过 $store 调用 state 中的 city;
(❗️$store 指的 就是我们创建的仓库 Store,而每个子组件
能使用 $store,是因为创建根 Vue 实例时,把 store 传入
进去了。) -->
<span class="iconfont arrow-icon"></span>
</div>
</router-link>
</div>
</template>
<script>
export default {
name: 'HomeHeader'
/* 3️⃣-⑥:删除 props;
props: {
city: String
}
*/
}
</script>
<style lang="stylus" scoped>
@import '~styles/varibles.styl'
.header
display: flex
line-height: $headerHeight
color: #fff
background: $bgColor
.header-left
float: left
width: .64rem
.back-icon
display: block
text-align: center
font-size: .56rem
.header-input
flex: 1
margin-top: .12rem
margin-left: .2rem
padding-left: .12rem
height: .64rem
line-height: .64rem
color: #ccc
background: #fff
border-radius: .1rem
.header-right
float: right
width: 1.24rem
text-align: center
color: #fff
.arrow-icon
margin-left: -0.1rem
</style>
同时,城市选择列表页的“当前城市”,也应该与首页头部的城市名相同。
3️⃣-⑧:打开 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.$store.state.city}} <!-- 3️⃣-⑨:原本写死的 城市“北京”,
现在从 Store 中获取数据 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 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>
<ul class="item-list">
<li class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">
{{innerItem.name}}
</li>
</ul>
</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 () {
const element = this.$refs[this.letter][0]
this.scroll.scrollToElement(element)
}
}
}
</script>
<style lang="stylus" scoped>
.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-left: .2rem
line-height: .54rem
background: #eee
color: #666
font-size: .26rem
.button-list
overflow: hidden
padding: .1rem .6rem .1rem .1rem
.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>
保存后,返回页面查看。当更改 Store 里公用数据中的 city 时,首页的“城市”和城市选择页的“当前城市”都会跟着发生变化:
OK,首页成功获取到公用数据之后,下一步,就可以完成我们的需求了:当点击城市选择页的“城市”时,首页的“城市”跟着发生变化。即,city 中的子组件 List.vue 需要改变仓库中 State 的数据 city 。
4️⃣打开 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.$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 of hot"
:key="item.id"
@click="handleCityClick(item.name)"
> <!-- 4️⃣-①:给每个“城市”按钮都绑定一个点击事件,触发时调用 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>
<ul class="item-list">
<li class="item border-bottom" v-for="innerItem of item" :key="innerItem.id">
{{innerItem.name}}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
name: 'CityList',
props: {
hot: Array,
cities: Object,
letter: String
},
methods: { // 4️⃣-②:在 methods 中定义 handleCityClick 方法;
handleCityClick (city) { // 4️⃣-③:方法接收一个参数 city;
this.$store.dispatch('changeCity', city) /*
4️⃣-④:当城市被点击时,通过 this.$store
的 dispatch 方法触发一个名叫 changeCity
的 action,并将接收到的 city 作为第二个参数;
*/
}
},
watch: {
letter () {
const element = this.$refs[this.letter][0]
this.scroll.scrollToElement(element)
}
},
mounted () {
this.scroll = new BScroll(this.$refs.wrapper)
}
}
</script>
<style lang="stylus" scoped>
.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-left: .2rem
line-height: .54rem
background: #eee
color: #666
font-size: .26rem
.button-list
overflow: hidden
padding: .1rem .6rem .1rem .1rem
.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>
4️⃣-⑤:打开 store 下的 index.js ;
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '上海'
},
actions: { // 4️⃣-⑥:在 Store 中,增加一个 actions 对象;
changeCity (ctx, city) { /*
4️⃣-⑦:actions 中有一个与 List.vue 触发的名字一样的 action,
即 changeCity;
(❗️changeCity 是一个方法,它接收两个参数:第一个 ctx 是上下文
context,第二个参数是传递过来的数据 city。)
*/
ctx.commit('changeCity', city) /*
4️⃣-⑧:changeCity 这个 action,通过 commit 方法,
触发一个名叫 changeCity 的 mutation,并将 city 作为
第二个参数传递过去;
(❗️changeCity 接收的第一个参数 ctx,可以帮助我们拿到
commit 方法;mutation 的名字可另起,不必与 action
的名字 changeCity 相同。)
*/
}
},
mutations: { // 4️⃣-⑨:Store 中,增加一个 mutations 对象;
changeCity (state, city) { /*
4️⃣-⑩:changeCity 同样接收两个参数,第一个是 state(指所有的
公用数据),第二个是传递进来的 city;
*/
state.city = city /*
4️⃣-⑪: 在 changeCity 这个 mutation 中,让 state 中的 city 等于
传过来的 city;
*/
}
}
})
保存后,返回页面查看。当点击“热门城市”时,首页的“城市”和“当前城市”都会跟着发生改变:
OK,现在我们已经完整走了一遍流程,再简单回忆一下更改数据的流程:
- 需要更改数据的子组件中,通过 Dispatch 方法调用
action; - Store 中的
actions通过 Commit 方法调用mutation; mutation完成数据的修改。
前面我们说过,“如果是异步的操作,或者比较复杂的、批量的同步操作,放在 Actions 里。有时,我们也可以越过 Actions,让组件直接去调用 Mutations 修改 State 中的数据”。
而在我们的项目中,更改 State 的过程中,并没有什么异步的操作,且这个操作也非常简单,不是批量的操作。所以此时,组件没必要去调用 Actions 做一次转发,它直接调用 Mutations 就可以了。
4️⃣-⑫:打开 store 中的 index.js ;
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '上海'
}, // ❗️删掉 actions 这部分的内容。
mutations: {
changeCity (state, city) {
state.city = city
}
}
})
4️⃣-⑬:打开 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.$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 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>
<ul class="item-list">
<li
class="item border-bottom"
v-for="innerItem of item"
:key="innerItem.id"
@click="handleCityClick(innerItem.name)"
> <!-- 4️⃣-⑮:给列表中的每个“城市”绑定事件,触发后执行 handleCityClick,
并传递一个参数“城市名” innerItem.name; -->
{{innerItem.name}}
</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import BScroll from 'better-scroll'
export default {
name: 'CityList',
props: {
hot: Array,
cities: Object,
letter: String
},
methods: {
handleCityClick (city) { // 4️⃣-⑭:通过 commit 方法直接调用 mutation;
this.$store.commit('changeCity', city)
}
},
watch: {
letter () {
const element = this.$refs[this.letter][0]
this.scroll.scrollToElement(element)
}
},
mounted () {
this.scroll = new BScroll(this.$refs.wrapper)
}
}
</script>
<style lang="stylus" scoped>
.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-left: .2rem
line-height: .54rem
background: #eee
color: #666
font-size: .26rem
.button-list
overflow: hidden
padding: .1rem .6rem .1rem .1rem
.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>
4️⃣-⑯:打开 city 下 components 中的 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 of list"
:key="item.id"
@click="handleCityClick(item.name)"
> <!-- 4️⃣-⑰:给每一项搜索出的城市绑定事件,触发时执行 handleCityClick 方法,
并传入搜索出的“城市名”; -->
{{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
}
},
methods: { // 4️⃣-⑱:在 methods 中定义 handleCityClick 方法;
handleCityClick (city) { /*
4️⃣-⑲:方法接收一个参数 city,执行时通过 commit 直接调用
mutation 更改数据。
*/
this.$store.commit('changeCity', city)
}
},
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%
padding: 0 .1rem
height: .62rem
line-height: .62rem
color: #666
text-align: center
border-radius: .06rem
.search-content
overflow: hidden
position: absolute
top: 1.58rem
left: 0
right: 0
bottom: 0
z-index: 1
background: #eee
.search-item
padding-left: .2rem
line-height: .62rem
background: #fff
color: #666
</style>
保存后,返回页面查看。省略了 Actions 这个步骤后,当点击城市选择页的任意城市名时,首页也会同步更改:
3 实现切换城市后的页面跳转
🔗前置知识:
《JavaScript 基础——浏览器提供的对象:② DOM》
实现网页上的页面跳转有两种方式:
- 一种是通过
<a>标签的方式; - 一种是通过 JS 的方式(如
location.href)。
在 Vue 中,也有两种方式:
- 一种是通过
<router-link>标签的方式; - 另一种也是通过 JS 方式实现跳转。
但在 Vue Router 中,JS 方式与之前不同,它用的是编程式导航的形式。编程式导航提供给我们一个 push 方法,帮助我们做页面跳转。
使用这个方法,我们就可以完成“点击城市后,跳转至首页”的功能。
5️⃣打开 city 下 components 中的 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 of 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
}
},
methods: {
handleCityClick (city) {
this.$store.commit('changeCity', city)
this.$router.push('/') /*
5️⃣-①:改变了城市的内容后,通过实例属性 $router 上的 push 方法,
跳转至“首页 /”;
(❗️因为项目中引入了 Vue Router,所以每一个组件里都有 $router
这样一个实例属性;)
(❗️想要跳转至哪一个页面,就 push 哪个地址。)
*/
}
},
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%
padding: 0 .1rem
height: .62rem
line-height: .62rem
color: #666
text-align: center
border-radius: .06rem
.search-content
overflow: hidden
position: absolute
top: 1.58rem
left: 0
right: 0
bottom: 0
z-index: 1
background: #eee
.search-item
padding-left: .2rem
line-height: .62rem
background: #fff
color: #666
</style>
5️⃣-②:打开 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.$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 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>
<ul class="item-list">
<li
class="item border-bottom"
v-for="innerItem of item"
:key="innerItem.id"
@click="handleCityClick(innerItem.name)"
>
{{innerItem.name}}
</li>
</ul>
</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('/') /*
5️⃣-③:改变了城市的内容后,通过实例属性 $router 上的 push 方法,
跳转至“首页 /”;
*/
}
},
watch: {
letter () {
const element = this.$refs[this.letter][0]
this.scroll.scrollToElement(element)
}
},
mounted () {
this.scroll = new BScroll(this.$refs.wrapper)
}
}
</script>
<style lang="stylus" scoped>
.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-left: .2rem
line-height: .54rem
background: #eee
color: #666
font-size: .26rem
.button-list
overflow: hidden
padding: .1rem .6rem .1rem .1rem
.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>
保存后,返回页面查看:
以上,我们就实现了“首页”与“城市选择页”之间的联动。
🏆本篇总结:
- 使用 Vuex 实现组件间数据共享;
- 使用编程式导航实现页面跳转。
祝好,qdywxs ♥ you!