课程章节: 项目实战 - 旅游网站城市列表页面开发
主讲老师: Dell
课程内容:
今天学习的内容包括:
v-route用来进行路由,页面的切换,比如从首页进入城市选择页面,先在router中的index.js配置文件中配置路由路径,然后再去需要切换界面的标签那里用router-link将跳转标签包裹,再用to写上需要跳转的路径即可,"/"表示根路径
a标签默认点击后变色,可以进行设置
后退按钮回到主页:箭头外层包裹一个<router-link to='/'>
vue组件想改store中的数据:
(1)vue组件先通过dispatch方法,调用actions,在actions中做一些异步处理和批量的同步操作
(2)actions通过commit方法,调用mutations
(3)通过mutations,改变state中公用数据的值
(4)(不用写代码)当state中数据发生改变,组件的视图改变
课程收获:
8.1 心得:
首先配置路由
在router中的index.js中,
import City from '@pages/city/City'
export default new Router({
routes: [{
path: '/',
name: 'Home',
component: Home
}, {
path:'/city',
name:'City',
component:City
}]
})
router-link帮助我们做页面跳转的内置组件
<router-link to='/city'>
<div class="header-right">
{{this.city}}
</div>
</router-link>
设置路径,名字,组件名:
routes:{
path:‘路径名称’,
name:‘引入路径的别名’,
component;‘组件名称’
}
页面跳转设置
<router-link to='路径名称'></router-link>
8.2 心得:
aceholder 属性提供可描述输入字段预期值的提示信息(hint)。
该提示会在输入字段为空时显示,并会在字段获得焦点时消失。
City.vue中template返回的模板必须由一个外层的标签包裹住
处理input输入框时,左右留一些间隙
padding:0 .1rem
8.3 心得:
修改1像素边框颜色:
.border-topbottom{
&:before
border-color:red;
&:after
border-color:red;
}
1px问题,不占高度:
.border-topbottom
&:beforeborder-color: #ccc
&:afterborder-color: #ccc
8.3 心得:
问题:页面没法拖动
解决办法:采用第三方的better-scroll
1.使用better-scroll需要符合better-scrollDOM结构
<div class="wrapper">
<ul class="content">
<li>...</li>
<li>...</li>
... </ul>
<!--您可以在这里放置一些其他DOM,它不会影响滚动--></div>
2.引入BScroll
import BScroll from 'better-scroll
3.创建BScroll的实例,创建时要接收一个DOM元素/DOM选择器
const scroll = new BScroll ('.wrapper')
4.ref可以帮助获取DOM
<div class="list" ref="wrapper">
5.页面会在挂载完毕后执行
mounted() {
this.scroll = new BScroll(this.$refs.wrapper)
}
字母表布局:
让abcd…在垂直方向居中
.list
display:flex
flex-direction:column //垂直地显示弹性项目
justify-content:center
水平居中
.item
text-align: center
better-scroll滚动插件的使用
npm install better-scroll -save 安装
important Bscroll from ‘better-scroll’ //引入插件
html滚动结构需要用一个div包裹,vue通过ref=“wrapper” 获取dom结构
<div class="container">
<div class="content" ref="wrapper">
<ul>
<li></li>
<li></li>
<li></li>
<li></li>
<li></li>
</ul>
</div>
</div>
mounted(){
this.scroll = new Bscroll( this.$refs.wrapper ); //初始化滚动插件
}
8.5 心得:
当我们获取到json数据时,发现Better-scroll滚动效果失效。
解决方案:在updated钩子内调用Better-scroll的refresh()方法进行刷新。
作用:重新计算 better-scroll,当 DOM 结构发生变化的时候务必要调用确保滚动的效果正常
updated () {
this.scroll.refresh()
}
对于ajax请求的浅析:
①在mounted钩子函数里面定义一个A方法,只要页面加载完毕就执行A方法
②A方法用于获取后端的数据,它是一个promise函数,主要获取到了函数就then一个B函数
③B函数可以接受一个res参数,这个参数就是返回的结果
<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>
8.6 心得:
方法一:Alphabet --> 父组件City --> List组件
1 Alphabet中,向外传递change事件,携带内容e.target.innerText
handelLetterClick (e) {
this.$emit('change', e.target.innerText)
}
2 City监听事件 @change='handleLetterChange'
父组件参数会接收到子组件传递的数据
handleLetterChange (letter) {
this.letter = letter
console.log(letter)
}
3 父组件再传给子组件 属性老三样
4 组件中通过侦听器watch
watch(){
letter () {
if(this.letter) {
const element = this.$refs[this.letter][0]
this.scroll.srollToElement(element)
}
}
}
$refs[this.letter]返回的数组
字母滚动事件
<li
@touchstart="handleTouchStart"
@touchmove="handleTouchMove"
@touchend="handleTouchEnd"
>
data中需要一个标识位:
touchStatus: false
方法中:
handleTouchStart () {
this.touchStatus = true
},
handleTouchMove (e) {
if (this.touchStatus) {
const startY = this.$refs['A'][0].offsetTop
const touchY = e.touches[0].clientY - 79
//e.touches[0]代表一个手指触碰
const index = Math.floor((touchY - startY) / 20)
if(index >=0 && index < this.letters.length){
this.$emit('change', this.letters[index])
}
}
},
handleTouchEnd () {
this.touchStatus = false
}
算法:
1 A字母到顶部的差值
2 滑动时距离顶部的高度
3 做差/字母高度,得出第几个字母
定义一个数组类型的数据:
computed: {
letters () {
const letters = []
for (let i in this.cities) {
letters.push(i)
}
return letters
}
}
循环时可以直接循环letters
vue中想要获取被点击的dom节点 需要通过点击事件获取到事件对象,然后使用e.target来获取到dom节点
handClick(e){
e.target //dom节点
e.target.innerText //dom节点里面的文本内容
}
vue中子组件想要互相传值:
子组件通过this.$emit( ’ change ’ , ’ msg ’ )传递给父组件一个自定义的change事件和一个msg值,父组件通过监听change事件来获取到传递过来的值并将其赋予到事先准备好的空数据上,然后子组件通过props接受父组件传递过来的另一个子组件的值,接收到的数据要用watch来监听变化才能正确执行对应的函数
<child1 @change="handGet"></child1>
<child2 :msg='msg'></child2>
data(){
return{
msg:''
}
},
handGet(res){
this.msg = res;
}
betterscroll插件滚动方法
this.scroll.scrollToElement( )里面要传递一个dom才能正确滚动到对应位置
methods: {
handTouchStart() {
this.touchStatus = true;
},
handTouchMove(e) {
if (this.touchStatus) {
//获取第一个字母到父元素顶部的距离
const startY = this.$refs['A'][0].offsetTop;
//获取手指触摸距离浏览器顶部的距离
const touchY = e.touches[0].clientY;
//通过手指触摸的坐标减去首字母距离父元素的距离再去掉父元素距离浏览器顶部的距离可得出手指触摸所在字母栏的位置,再除以每个字母所占空间20可得出手指触摸停留在哪一个字母
const font = (touchY - startY - 79) / 20;
const letter = parseInt(font);
//当超出字母栏时不执行传值滚动
if (letter >= 0 && letter < this.letters.length) {
this.$emit('scroll', this.letters[letter]);
}
}
},
handTouchEnd() {
this.touchStatus = false;
},
handClick(e) {
this.$emit('change', e.target.innerText);
}
}
8.7 心得:
设置startY初始值为0
设置updated生命周期钩子
当页面数据被更新时,同时页面完成自己渲染时,updated钩子就会执行
updated() {
this.startY = this.$refs['A'][0].offsetTop
}
通过函数节流限制函数执行的频率
在数据项中定义timer 默认值为null
data:{
startY:0
timer:null
}
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-startY) / 20)
if(index >= 0 && index < this.letters.length){
this.$emit('change',this.letters[index])
}
},8)
}
}
节约handleTouchMove函数的执行频率,提高网页的性能
8.8 心得:
搜索功能的思路:
做一个输入框与数据的双向绑定
在数据中定义一个存放搜索结果的数据
通过watch监听输入框的数据变化来进行请求数据处理
在数据上定义一个数据记录定时器的状态,使用定时器是为了函数节流提升性能
data() {
return {
keyword: '', //与输入框做双向绑定
list: [], //存放最终的搜索结果
timer: null //记录定时器的状态是否存在
};
},
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) => {
判断拼音或者汉字有一个相同就将该数据存入事先准备的变量result里面
if (value.spell.indexOf(this.keyword) > -1 || value.name.indexOf(this.keyword) > -1) {
result.push(value);
}
});
}
//将关联的数据的变量结果赋值给data里面准备好的变量
this.list = result;
}, 300);
}
}
8.9 心得:
城市选择页面和首页共享数据
state: 公共数据存储的地方
组件改数据:
必须先调用Actions,进行异步处理/批量同步操作(非绝对必要,有时可跳过 ),通过dispatch方法
Acitions再调用Mutations,通过commit方法
Mutations最终改变公用数据的值
数据改变,页面改变
Vue.use(Vuex):
安装vuex依赖:
cnpm install vuex@3.0.1 --save
Vue.use(Vuex):
vue中使用插件
- src目录下创建store文件夹,当中写入index.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
city: '北京'
}
})
-
输出一个Store,公用数据state;首页与城市页面中城市为公用数据
-
main.js 中放入store
import store from './store'
new Vue({
el: '#app',
router,
store,
components: { App },
template: '<App/>'
})
- 在根实例中创建store方法
首页header中的city内容为前端传递,去掉
显示首页的city:
{{this.$store.state.city}}
通过修改数据,修改页面显示:
<div
class="button-wrapper"
v-for="item of hot"
@click="handleCityClick(item.name)"
>
handleCityClick (city) {
//alert(city)
this.$store.dispatch('changeCity', city)
//派发一个名为changeCity的Action,传递city
}
回到/store/index.js添加actions
actions: {
changeCity (ctx, city) {
//上下文,数据
//console.log (city)
ctx.commit('changeCity', city)
}
}
继续创建mutations,mutation执行需要commit方法
mutations: {
changeCity (state, city) {
//公用数据
state.city = city
}
}
组件中可以直接用$commit方法调用mutations,没有异步操作和批量操作的话
编程式导航:
除了<router-link>a
标签定义导航连接,还可以通过router实例方法
this.$router.push('/')
8.10 心得:
H5中的localStorage API,实现类似cookie
mutations中设置localStorage.city = city
state中city 如果取不到localStorage.city 就取上海
state: {
city: localStorage.city || '上海 '
},
mutations: {
cityChange (state, city) {
state.city = city
localStorage.city = city
}
}
使用localStorage 要注意外层包裹一个try catch =>某些浏览器用户关闭本地存储/隐身模式,浏览器直接抛出异常
let defaultCity = '上海'
try {
if (localStorage.city) {
defaultCity = localStorage.city
}
} catch(e){}
mutations: {
cityChange (state, city) {
state.city = city
try {
localStorage.city = city
} catch(e){}
}
}
vuex中的高级API
import { mapState } from ‘vuex’
computed: {
...mapState(['city'])
}
mapState 把vuex中的数据映射到computed的计算属性里,映射数据city到名为city的计算属性中
computed: {
...mapState({
currentCity: 'city'
})
}
把vuex中的公用数据city映射到computed的计算属性里,
映射的名字叫currentCity
mapMutations:
methods: {
...mapMutations(['changeCity'])
}
有一个mutation叫changeCity,将这个mutations映射到组件中叫mutation的方法里,直接传入changeCity即可,数据传入city
methods: {
this.changeCity(city)
...mapMutations(['changeCity'])
}
getters的作用:类似组件的计算属性,根据state中的数据,计算出新的数据
getters: {
doubleCity (state) {
return state.city + ' ' + state.city
}
}
header.vue中
…mapGetters(’[doubleCity]’)
module的使用:管理复杂项目进行拆分,具备更好的可维护性
8.11 心得:
将公用数据都存放在state中,然后数据传送到各个组件中
在需要改变数据时,首先使用dispatch调用actions实现一些异步的操作,然后actions使用commit调用mutations进行数据的修改,最终将修改后的数据传到各个组件中。
8.12 心得:
创建分支:city-keepalive
git pull
git checkout city-keepalive
启动项目,在控制台中打开“网络”标签,切换到XHR标签中,可以看到每次切换页面,ajax都会重新请求数据。
原因如下:
例如Home.vue这个组件,每次打开时,都会重新渲染,那么mounted()这个钩子函数都是被重新执行,函数中的ajajx请求也就会重新进行。
改进方法:只获取一次,以后不要重复获取。
解决方法:
在main.js中可以看到,程序的入口组件是“App”这个组件。打开App.vue,显示的是路由对应的内容。在外层包裹vue自带的一个标签:,含义是:路由的内容被加载之后,这个路由中的内容就被暂放在内存中,下次再进这个路由,不需要重新渲染组件,而是直接从内存中调数据,也就不需要重新执行钩子函数,从而能够阻止反复发送ajax请求。
实际使用中,当城市改变了,首页中对应的城市景点内容也应该随之改变,也就是当城市切换的时候,需要重新发送ajax请求。解决方式如下:
(1)Home.vue中:
添加计算属性:
修改ajax请求,添加city参数:
当在App.vue中使用标签时,在Home.vue中会多出一个生命周期钩子函数:activated:当页面重新显示的时候,会调用activated钩子函数。在这个函数中判断当前的页面中的城市和上一次页面中显示的城市是否相同,如果不相同,再重新发送ajax请求。
(2)Home.vue中data中增加lastCity
(3)mounted中:
(4)页面重新激活执行actived函数:
提交代码
git add .
git commit -m 'finish-all-pages’
git push
git checkout master
git merge city-keepalive
8.13 心得:
① 解决300ms延迟:用样式代替fastClick库
html{ touch-action: manipulation; }
② 自定义延时器,间隔时间 16ms → 8ms
③
import BetterScroll from 'better-scroll’
mounted () {
this.scroll = new BetterScroll(this.$refs.wrapper, {
click: true //创建时定义,解决无法点击的bug
})
}