better-scroll实现左右联动时打印,this.$refs['热销榜']显示undefinded

问题描述

console.log(this.letter) //A B C D E...点击哪个打印哪个

consol.log(this.$refs[this.letter]) //undefined,

//vue直接报错"Error in callback for watcher "letter": "TypeError: Cannot read property '0' of undefined""
consol.log(this.$refsthis.letter)

console.log(this.$refs[0]);//undefined
console.log(this.$refs.热门榜);//undefined
console.log(typeof this.letter) //打印出来类型是string
console.log(this.letter instanceof String) //输出的确实false

clipboard.png

问题出现的环境背景

采用的方法是,当点击字母时,触发事件将e.target.innerHTML通过$emit('letterChange', e.target.innerHTML)传给根组件,根组件再将数据传给component动态组件,最后分别在境外目的地和境内目的地子组件中用props接收,并用watch监听数据的变化,当变化时获取this.$refs[this.letter][0],并计算其距离顶部的距离,然后设置scrolltop,已达到滚动到指定位置的目的。DOM中的列表用循环字母表事先绑定了ref。

尝试的方法

看到网上上一个跟我差不多问题的朋友试过去掉空格的原因已经成功了,但是我的还是不行。

相关代码

goods.vue

<template>
    <div class="goods">
      <menu-wrapper
        :goods="goods"
        @change="handleLetterChange"
      >
      </menu-wrapper>
      <foods-wrapper
        :goods="goods"
        :letter="letter"
      ></foods-wrapper>
    </div>
</template>

<script type="text/ecmascript-6">
 // import Bscroll from 'better-scroll';
  import MenuWrapper from './components/MenuWrapper.vue';
  import FoodsWrapper from './components/FoodsWrapper.vue';

  export default {
    name: 'goods',
    props: {
      goods: Array,
    },
    components: {
      MenuWrapper,
      FoodsWrapper,
    },
    data() {
      return {
        letter: '',
        rightHeight: [], // 右侧列表滑动的Y轴坐标
        scrollY: 0, // 右侧列表滑动的Y轴坐标
        rightLiTops: [], // 所有分类头部位置
      };
    },
    methods: {
      handleLetterChange(letter) {
        this.letter = letter;
      },
    },
  };
</script>

<style lang="scss" scoped>
  @import "../../common/global.scss";
  .goods{
    display: flex;
    position: absolute;
    top: px2rem(174);
    bottom: px2rem(46);
    width: 100%;
    overflow: hidden;
  }
</style>

MenuWrapper.vue

<template>
    <div class="menu" ref="menu">
      <div class="menu-wrapper">
        <ul>
          <li
            v-for="(item,index) in goods"
            class="menu-item"
            :key="index"
            :ref="index"
            @click="handleLetterClick"
          >
            <span class="text border-1px">
              <span
                v-show="item.type>0"
                class="icon"
                :class="classMap[item.type]"
              ></span>{{item.name}}
              </span>
          </li>
        </ul>
      </div>
    </div>
</template>

<script>
  import Bscroll from 'better-scroll';
  export default {
    name: 'MenuWrapper',
    props: {
      goods: Array,
    },
    created() {
      this.classMap = ['decrease', 'discount', 'guarantee', 'invoice', 'special'];
    },
    methods: {
      handleLetterClick(e) {
        this.$emit('change', e.target.innerText);
      },
    },
    mounted() {
      // this.scroll = new Bscroll(this.$refs.menu);
      this.menuBscroll = new Bscroll(this.$refs.menu, {
        click: true,
      });
    },
  };
</script>

<style lang="scss" scoped>
  @import "../../../common/global.scss";
  .menu{
    flex: 0 0 px2rem(80);
    width: px2rem(80);
    background:#f3f5f7;
    .menu-wrapper{
      .menu-item{
        display: table;
        height: px2rem(54);
        width: px2rem(56);
        line-height: px2rem(14);
        padding: px2rem(0) px2rem(12);
        &.current{
          position: relative;
          z-index: 10;
          margin-top: px2rem(-1);
          background: #fff;
          font-weight: 700;
          .text{
            @include border-none;
          }
        }
        .icon{
          display: inline-block;
          vertical-align: top;
          width: px2rem(12);
          height: px2rem(12);
          margin-right: px2rem(2);
          background-size: px2rem(12) px2rem(12);
          background-repeat: no-repeat;
            &.decrease{
              @include bg-image('decrease_3');
            }
            &.discount{
              @include bg-image('discount_3');
            }
            &.guarantee{
              @include bg-image('guarantee_3');
            }
            &.invoice{
              @include bg-image('invoice_3');
            }
            &.special{
              @include bg-image('invoice_3')
            }
        }
        .text{
          display: table-cell;
          width: px2rem(56);
          vertical-align: middle;
          @include border-1px(rgba(7,17,27,0.1));
          font-size: px2rem(12);
        }
      }
    }
  }
</style>


FoodWrapper.vue

<template>
  <div class="foods"  ref="food">
    <div>
      <div class="food-wrapper">
        <ul>
          <li v-for="(item,key) in goods"
              :key="key"
              @change="letter"
              :ref="item"
              class="food-list food-list-hook">
            <h1 class="title">{{item.name}}</h1>
            <ul>
              <li v-for="(food,index) in item.foods"
                  :key="index"
                  class="food-item border-1px"
              >
                <div class="icon">
                  <img width="57" height="57" :src="food.icon">
                </div>
                <div class="content">
                  <h2 class="name">
                    {{food.name}}
                  </h2>
                  <p class="desc">
                    {{food.description}}
                  </p>
                  <div class="extra">
                    <span class="count">月售{{food.sellCount}}</span>
                    <span>好评率{{food.rating}}%</span>
                  </div>
                  <div class="price">
                    <span class="now">¥{{food.price}}</span>
                    <span class="old" v-show="food.oldPrice">¥{{food.oldPrice}}</span>
                  </div>
                </div>
              </li>
            </ul>
          </li>
        </ul>
      </div>
    </div>
  </div>
</template>

<script>
   import Bscroll from 'better-scroll';
  // eslint-disable-next-line no-unused-vars
  import Event from './bus';

  export default {
    name: 'FoodsWrapper',
    props: {
      goods: Array,
      letter: String,
    },
    mounted() {
      this.rightBcroll = new Bscroll(this.$refs.food, {
        probeType: 3, // 在滚动中触发scroll事件
      });
    },
    watch: {
      letter() {
        if (this.letter) {
          // if (this.letter && this.$refs[this.letter]) {
          //   const element = this.$refs[this.letter][0];
          //   this.scroll.scrollToElement(element);
          // }
          // const freshLetter = this.letter.replace(/\s/g, '&nbsp;');
          // const element = this.$refs[freshLetter][0];
          // this.scroll.scrollToElement(element);
          console.log(this.$refs);
          console.log(this.letter);
        }
      },
    },
  };
</script>

<style lang="scss" rel="stylesheet" scoped>
  @import "../../../common/global.scss";
  .foods{
    flex: 1;
    .food-wrapper{
      .food-list{
        .title{
          padding-left: px2rem(14);
          height: px2rem(26);
          line-height: px2rem(26);
          border-left: px2rem(2) solid #d9dde1;
          color: rgb(147,153,159);
          background: #f3f5f7;
          font-size: px2rem(12);
        }
        .food-item{
          display: flex;
          margin: px2rem(18);
          padding-bottom: px2rem(18);
          @include border-1px(rgba(7,17,27,0.1));
          &:last-child{
            @include border-none();
            margin-bottom: px2rem(0);
          }
          .icon{
            flex: 0 0 px2rem(57);
            margin-right: px2rem(10);
          }
          .content{
            flex: 1;
            .name{
              margin: px2rem(2) px2rem(0) px2rem(8) px2rem(0);
              height: px2rem(14);
              line-height: px2rem(14);
              font-size: px2rem(14);
              color: rgb(7,17,27);
            }
            .desc,.extra{
              line-height: px2rem(10);
              font-size: px2rem(10);
              color: rgb(147,153,159);
            }
            .desc{
              margin-bottom:px2rem(8);
              line-height: px2rem(12);
            }
            .extra{
              .count{
                margin-right: px2rem(12);
              }
            }
            .price{
              font-weight: 700;
              line-height: px2rem(24);
              .now{
                margin-right: px2rem(8);
                font-size: px2rem(14);
                color: rgb(240,20,20);
              }
              .old{
                text-decoration: line-through;
                font-size: px2rem(10);
                color: rgb(147,153,159);
              }
            }
          }
        }
      }
    }
  }

</style>

你期待的结果是什么?实际看到的错误信息又是什么?

希望有大神能棒我解答一下这个问题,被困扰了很久。其实我有点困惑this.$refs打印出来的是什么东西,感觉好像有点被搞懵逼了.

根据大佬们的回答修改了代码

:ref="item.name"
console.log=this.$refs[this.letter]
打印出来的是

clipboard.png

console.log=this.$refs//显示的是下图

clipboard.png

现在显示的报错是: [Vue warn]: Error in callback for watcher "letter":
"TypeError: this.scrollToElement is not a function"

完整解决办法是
第一步是FoodWrapper里面动态绑定的:refs改成
:ref="item.name"
第二步是右边跳转到相应的位置
this.scrollToElement改成this.rightBcroll.scrollToElement

阅读 3.9k
3 个回答

MenuWrapper.vue 中的 handleLetterClick 函数向父组件提交的值看起来应该是 goods.item.name,因此 FoodWrapper.vue 得到的props.letter 也是这个, 想要通过 this.$refs[this.letter]获取到目标元素应该做下面的修改。

FoodWrapper.vue

<li v-for="(item,key) in goods"
  :key="key"
  @change="letter"
  :ref="item.name"
  class="food-list food-list-hook">
 ---snip---
</li>

<li v-for="(item,key) in goods"
:key="key"
@change="letter"
:ref="item.name"
class="food-list food-list-hook">
</li>

然后再调用this.rightBcroll.scrollToElement(element)

首先this.$refs是一个对象。里面包括当前实例含有ref属性的组件。
然后,我觉得你可以把左侧的点击的序列传到右侧,
调用 this.rightBcroll.scrollToElement(this.$refs.item[index])

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题