Vant: [Bug Report] transition 导致特定情况下 list 组件初始无法触发加载

Created on 30 Aug 2019  ·  12Comments  ·  Source: youzan/vant

设备 / 浏览器

任意

Vant 版本

2.1.8

Vue版本

2.6.10

重现链接

https://codesandbox.io/embed/vant-issue-moban-l9y3q

描述问题

demo 下来回切换 foo , bar Tab

🐞 bug

Most helpful comment

这个问题还是由于切换后 <van-list /> 在可视区域外,因此导致不会触发 onLoad 事件

  • 问:为什么快速的在 Foo 和 Bar 页面之间切换时,有时候触发 onLoad 呢?

  • 答:首先,需要注意的一个点是:页面在左右切换时,它们的 UI 排列布局是「上下」的,而不是「左右」的。假设当前页面是 Foo,然后快速点击 Bar --> Foo --> Bar 时,切换大致过程如下:

    • Bar:此时由于 Foo 是一屏高,因此 Bar 是屏幕区域外,不会触发 onLoad

    • Foo:由于上次 Bar 未加载内容,因此高度是 0,所以此次 Foo 是在可视区域内,会触发 onLoad,开始加载数据

    • Bar:如果速度够快,切换会早于上次 Foo 加载数据完成前,此时 Foo 的高度也可认为是 0,所以这次 Bar 也是在可视区域内,所以会触发 onLoad

可以在 Layout.vue 里添加以下样式,延迟页面切换动画,然后观察这个过程

.van-slide-left-enter-active {
  animation-duration: 10s;
}
.van-slide-left-leave-active {
  animation-duration: 10s;
}

解决方案是:

  1. 切换时使用 this.$refs.list.check(); 手动触发,或
  2. 保证页面切换时,布局是左右的,而不是上下的

All 12 comments

@ystarlongzi 有空帮忙看下~

嗯嗯,好的,今天我看下

这个问题还是由于切换后 <van-list /> 在可视区域外,因此导致不会触发 onLoad 事件

  • 问:为什么快速的在 Foo 和 Bar 页面之间切换时,有时候触发 onLoad 呢?

  • 答:首先,需要注意的一个点是:页面在左右切换时,它们的 UI 排列布局是「上下」的,而不是「左右」的。假设当前页面是 Foo,然后快速点击 Bar --> Foo --> Bar 时,切换大致过程如下:

    • Bar:此时由于 Foo 是一屏高,因此 Bar 是屏幕区域外,不会触发 onLoad

    • Foo:由于上次 Bar 未加载内容,因此高度是 0,所以此次 Foo 是在可视区域内,会触发 onLoad,开始加载数据

    • Bar:如果速度够快,切换会早于上次 Foo 加载数据完成前,此时 Foo 的高度也可认为是 0,所以这次 Bar 也是在可视区域内,所以会触发 onLoad

可以在 Layout.vue 里添加以下样式,延迟页面切换动画,然后观察这个过程

.van-slide-left-enter-active {
  animation-duration: 10s;
}
.van-slide-left-leave-active {
  animation-duration: 10s;
}

解决方案是:

  1. 切换时使用 this.$refs.list.check(); 手动触发,或
  2. 保证页面切换时,布局是左右的,而不是上下的

👍到位

@ystarlongzi 十分感谢,但是涉及 list 的页面布局一般不太可能用左右,所以方案 2 是不太可能。方案一虽不是特别优雅,但我试了试,也没效果呢?https://codesandbox.io/embed/vant-issue-moban-l9y3q
麻烦再帮忙看下。

@kid1412621

更严谨的说话应该是在页面切换动画完成之后在触发 this.$refs.list.check();

但我发下在路由组件内部好像没办法监听切换动画是否完成,所以只能加个定时器了。如下:

mounted() {
  this.listCheckTimer = setTimeout(() => {
  this.$refs.list.check();

  // 因为切换动画时长是 300ms,所以这个定时器的时间一定要大于 300ms
  }, 350); 
},

beforeDestroy() {
  // 页面销毁前,记得清空计时器
  clearTimeout(this.listCheckTimer)
}

@ystarlongzi 再次感谢, 感觉有点 hack 的意思, 有没有更内建的解决方案呢?

嗯嗯,其实还有个方案我觉得比较合理,大致思路是:

不要依赖 onLoad 事件去加载数据(被动获取),还要结合用户的实际操作去加载数据(主动获取)

methods: {
  loadData() {
    if (this.loading) {
      return;
    }

    this.loading = true;

    // ........ 请求数据
  },

  onLoad() {
    // 被动加载数据
    this.loadData();
  }
},

mounted() {
  // 主动加载数据
  this.loadData();
}

另外,如果要使用这种方案,还需要将 <van-list v-model="loading" /> 修改为 <van-list :loading="loading" />,否则第一次加载数据之后,后面的将无法再加载更多。原因如下代码,可以看到它会先将 loading 赋值为 true,然后再触发 load 事件

https://github.com/youzan/vant/blob/2d7a188c2daeaf460bc15ed2b59e7bd2b7d76a2d/src/list/index.js#L91-L94

@chenjiahan 是不是调换下两个 emit 顺序,会相对合理些?

我觉得目前的事件触发顺序从逻辑上说没有问题,如果要实现上述的主动触发的需求,可以按下面这样写

methods: {
  onLoad() {
    // 请求数据
  },

  // 主动加载数据
  loadData() {
    if (!this.loading) {
      this.loading = true;
      this.onLoad();
    }
  }
},

mounted() {
  this.loadData();
}

嗯嗯,你这种方案是可行的。但假如调整了那两个 emit 的顺序,它能支持我们两个的写法,用户使用起来也是更「无感知」的

其实,我还是比较倾向 loading 的赋值位置应该是:在事件函数头部之后 、但在 ajax 之前(之后也可以,因为是异步的),个人的一些开发习惯 😹😹。大致代码如下:

loadXXX() {
  // 在函数最顶部,获取的 loading 值应该是「上次」的
  // ....... 一些逻辑判断
  // .......

  // ajax 前,去更新 loading
  loading = true;

  ajax().then(() => {
    // ajax 后,再更新 loading
    loading = false;
  });
}

对于组件来说,不会关心用户是通过异步请求还是同步读取缓存的方式去获取数据,只要触发 load 事件,就代表已经进入 loading 状态了

为什么不建议调整 emit 的顺序,一是目前的顺序从逻辑上是合理的,二是你不知道现有用户在 onLoad 中是否根据 loading 做了某些判断,如果有这样的逻辑,那我们改动就相当于造成了 breaking change

嗯嗯,明白了

Was this page helpful?
0 / 5 - 0 ratings