去哪儿-11-city-components

目标: 实现兄弟组件之间的联动

(一) 点击城市页面的Alphabet.vue中的字母能够跳转到对应的List.vue的位置

  1. 给Alphabet.vue中的每一个循环的li绑定一个点击事件<li class="item" v-for="(item,key) of cities" :key="key" @click="handleLetterClick">{{key}}</li>,并将这个事件定义到methods对象中,当点击某一个字母的时候这个事件会接收到一个事件对象e,console.log(e.target.innerText) 会发现,在页面上点击字母的时候在终端就会显示相应的字母。 希望的是: 我们把这个字母传递给List.vue组件, 让其对应的区块显示出来。

  2. 兄弟组件的传值: 兄弟组件是非父子组件,可以使用bus总线的方式,而这里的非父子组件就是一个兄弟组件,我们可以将Alphabet.vue中点击字母获取到的数据传递给City.vue组件,然后再由这个组件转发给List.vue组件

Alphabet.vue组件中触发点击事件获取到数据,使用this.$emit('change', e.target.innerText)的方式向外触发事件,事件的名字是change,携带的数据是e.target.innerText

然后, 由City.vue组件来监听这个事件,即<city-alphabet :cities="cities" @chang="handleLetterChange"></city-alphabet>。在methods中定义这个事件,并且它会接收到一个letter数据,这个数据就是由子组件Alphabet.vue传递过来的那个点击的字母。

现在,将letter转发给List.vue。这里就是父组件向子组件传递数据了,是通过属性的形式传递的, 首先在data函数中定义一个数据letter,默认为一个空字符串,当接收到外部传来的letter数据的时候,即:

handleLetterChange (letter) {
    this.letter = letter
}

最后就只需要将这个letter传递给city-list就可以了:<city-list :cities="cities" :hotCities="hotCities" :letter="letter"></city-list>

父组件传递了一个letter,子组件List.vue就要接收这个letter:

props: {
    cities: Object,
    hotCities: Array,
    letter: String
}

这个时候,子组件就监听到由父组件传递过来的这个letter的内容了。我们想要做的是: 当我监听到这个letter发生变化的时候,我要把本组件中与letter值相同的那个城市列表项显示出来。

实现:借助vue自带的侦听器watch,在watch中侦听letter的变化。better-scroll提供了一个接口scrollToElement(),它可以让better-scroll的滚动区自动的滚到某一个元素上。具体的,先给循环区域添加一个ref::ref="key" ,就可以通过this.$refs获取到这个字母this.letter对应的class="area"的这个区域:

watch: {
    letter () {
        if (this.letter) {
            const element = this.$refs[this.letter]
            this.scroll.scrollToElement(element)
        }
    }
}

上述代码会报错,原因是ref是通过循环输出的,this.$refs[this.letter]或得到的内容是一个数组而不是一个标准的DOM元素,this.scroll.scrollToElement(element)的参数element需要是一个DOM元素或者是一个DOM的选择器,所以需要修改为:

watch: {
    letter () {
        if (this.letter) {
            const element = this.$refs[this.letter][0]
            this.scroll.scrollToElement(element)
        }
    }
}

(二) 希望在右侧的字母表上做上下拖拽的时候,也会导致左侧的列表项的相应变动

首先,给Alphabet组件中的li绑定三个touch事件,即@touchstart="handleTouchStart" @touchmove="handleTouchMove" @touchend="handleTouchEnd",并定义相应的事件函数。然后,在data中设置一个标识位:touchStatus: false,当手指触摸的时候,将这个值设置为true,当手指停止触摸的时候设置为false。只有当touch状态为true的时候才去做move的处理,此时代码是这样的:

handleTouchStart () {
    this.touchStatus = true
},
handleTouchMove () {
    if (this.touchStatus) {
        
    }
},
handleTouchEnd () {
    this.touchStatus = false
}

其次,在右侧的字母表处上下滑动的时候,想要直接获取当前手指所在的是哪一个字母是比较困难的,我们的思路是: 首先获得A这个字母距离顶部的高度,然后获得当前滑动的时候距离顶部的高度,做一个差值,就能够获取当前字母与A字母之间的高度,再除以每个字母的高度,就可以知道当前是哪一个字母了。这样的话,去取对应的字母,触发一个change事件给外部就可以了。

如果想要根据下标找到这个下标对应的字母的话,就需要有个数组来存储这个字母的列表, 我们之前使用的cities是一个对象而不是一个数组,所以需要定义一个数组类型的数据,在这里设置一个计算属性:

computed: {
    letters () {
        const letters = []
        for (let i in this.cities) {
            letters.push(i)
        }
        return letters
    }
}

其返回的结果是一个数组,形式是[‘A’,‘B’,‘C’,…]。有了这个数组,之前的循环就可以做以下修改:

<ul>
    <li class="item" 
        v-for="item of letters" 
        :key="item"
        @click="handleLetterClick"
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd"
    >
        {{item}}</li>
</ul>

当我们上下拖动的时候,即this.touchStatus = true的时候,需要计算一下A字母距离顶部的高度,方法是:设置一个ref属性来获取A元素距离顶部的高度:

<ul>
    <li class="item" 
        v-for="item of letters" 
        :key="item"
        :ref="item"
        @click="handleLetterClick"
        @touchstart="handleTouchStart"
        @touchmove="handleTouchMove"
        @touchend="handleTouchEnd"
    >
        {{item}}</li>
</ul>

接下来,就可以按照之前的思路进行位置的计算:

handleTouchMove (e) {
    if (this.touchStatus) {
        const startY = this.$refs['A'][0].offsetTop
        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])
        }
        
    }
}

其中,const startY = this.$refs['A'][0].offsetTop计算的是字母A距离头部蓝色区域的下边沿的高度。handleTouchMove的时候会接收到一个事件参数对象e,在这个事件对象中会有一个touches的数组,第0项表示的就是我手指触摸屏幕的一些信息,于是可以获取到手指的clientY的值,而这个值表示的是当前的位置到屏幕最顶部的距离,要想计算当前位置到蓝色区域下边沿的高度,还要减去头部蓝色区域的高度,即e.touches[0].clientY - 79。index就是当前字母的下标了,公式中的20表示的是每个字母的高度。当索引值在0到this.letters.length之间的时候就可以向外触发change事件了,传递的数据就是this.letters[index]

(三) 列表切换性能优化

  1. handleTouchMove方法中,当我们的手指在右侧的字母表上进行上下滑动的时候,这个方法就会被执行,其中A字母的offsetTop一直都是固定的,而每次执行这个方法的时候,这里都会计算一次,所以性能就会比较低。解决方法: 在data中定义一个变量startY,初始值设置为0,然后再写一个updated的生命周期钩子,当页面的数据被更新的时候,并且页面完成了自己的渲染之后,updated这个钩子就会执行,即:
updated () {
    this.startY = this.$refs['A'][0].offsetTop
}

分析: 当初次渲染Alphabet.vue的时候, cities的数值化值是一个空对象,页面刚加载的时候,Alphabet.vue里面什么东西都不会显示出来,当City.vue通过ajax获取到数据之后,cities的值才发生变化,Alphabet.vue才会被渲染出来,当向Alphabet.vue中传递的数据发生变化的时候,它就会重新渲染,当Alphabet.vue被重新渲染之后,这个updated生命周期钩子就会被执行,这时,页面上已经展示出了字母列表的所有内容,这时去获取字母A的offsetTop就不会有问题,拖动效果依然能够实现。

  1. 函数节流:

当我们的鼠标在字母表上来回的移动的时候,touchmove执行的频率是非常高的,我们可以通过节流来限制一下函数执行的频率。

在数据项中定义一个变量timer,默认值为null,当在执行handleTouchMove的时候,首先在判断是否已经存在了,如果存在就清除掉这个timer,否则就创建一个timer:

handleTouchMove (e) {
    if (this.touchStatus) {
        if (this.timer) {
            clearInterval(this.timer)
        }
        this.timer = setTimeout(() => {
            const touchY = e.touches[0].clientY - 79
            const index = Math.floor((touchY - this.startY) / 20)
            if (index >= 0 && index < this.letters.length) {
                this.$emit('change', this.letters[index])
            }
        }, 16);
        
        
    }
}

优化完成。

代码提交

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值