从列表到详情,没你想的那么简单

前言

本文先假设我们使用的是 vue + vuex + vue-router 的情况来展开讨论,React 全家桶的情况应该类似。

在日常的前端研发中,我们经常会遇到如题的场景:比如从商品列表进入商品详情,从订单列表进入订单详情。先看一个 demo~

看起来是不是还算丝滑流畅,跟客户端效果较为接近~

正文开始

很多同学应该会说,这不是很容易么,用 vue-router + transition 就好啦。

<template>
  <transition name="custom-classes-transition"
    :enter-active-class="`animated ${transitionEnter}`"
    :leave-active-class="`animated ${transitionLeave}`">
    <router-view/>
  </transition>
</template>

<script>
export default {
  data: () => ({
    transitionEnter: ‘‘,
    transitionLeave: ‘‘
  }),
  watch: {
    ‘$route‘ (to, from) {
      const toDepth = to.path.split(‘/‘).length
      const fromDepth = from.path.split(‘/‘).length
      if (toDepth < fromDepth) {
        this.transitionEnter = ‘slideInLeft‘
        this.transitionLeave = ‘slideOutRight‘
      } else {
        this.transitionEnter = ‘slideInRight‘
        this.transitionLeave = ‘slideOutLeft‘
      }
    }
  }
}
</script>

如上所示,slide 的动画使用 animated.css

<style lang=‘scss‘>
$use-slideInLeft: true;
$use-slideInRight: true;
$use-slideOutLeft: true;
$use-slideOutRight: true;
import "node_modules/animate-sass/animate";
.animated {
  top: 0;
  width: 100%;
  height: 100%;
  animation-duration: calc(300ms);
}
.slideOutRight, .slideOutLeft {
  position: fixed;
}
</style>

然后定义好 router 的路径规则,笔者采用 restful 的方式命名。

export default new Router({
  routes: [{ // 商品列表
    path: ‘/‘,
    name: ‘auctionroom‘,
    component: () => import(‘app/auction-room/room/app.vue‘)
  }, {       // 商品详情
    path: ‘:activityId‘,
    name: ‘auctionroom-item‘,
    component: () => import(‘app/auction-room/item/app.vue‘)
  }]
})

真这么容易就能完成需求么?

墓碑元素和路由守卫

实际情况是,我们在进入详情页之前,并没有拿到详情的数据!一般都会选择在 vue 组件实例生命周期的 created 钩子,获取对应的后端数据接口。

而这个过程跟 transition 的动画是并行的,会出现右侧页面还未拿到数据就划入屏幕的情况。如大家所想,我们会尽量让数据源表现的像现实世界遇到的,比如有网络延迟等等。

在这种情况发生时,其实是需要放置一个墓碑条目占位在对应位置,等到数据取到后墓碑条目会被实际内容替代。这样设计的原因是,我们希望墓碑元素在被实际数据替代前可以有一个漂亮的过渡,而不是出现那种生硬的或者让人迷失的效果。

先略这个丑陋的墓碑,实际情况的墓碑元素应该是有设计的,在很多新闻客户端如今日头条中会见到~

比起这一种方案,更常见的方式是在导航完成前获取数据,使用 router 的 beforeRouteEnter 钩子。这个方式固然不错,但同样有潜在的问题:

  • 在获取数据时,用户会停留在当前的界面,因此建议在数据获取期间,显示一些进度条或者别的指示。如果数据获取失败,同样有必要展示一些全局的错误提醒。
  • 不!能!获取组件实例 this,因为当守卫执行前,组件实例还没被创建

vuex + keep-alive 和返回刷新

其实在渲染详情的时候,我们在当前列表已经有了一个商品的 Collection,一般是个数组。那为什么不能在进入详情时,使用当前已有的数据做填充呢?详情页将这些数据立即渲染,然后再通过接口获取其余部分的数据,等完整数据获取之后再回填到页面上~

采用这个方案,我们必须引入 vuex , 才能在多个页面组件之间传递数据(耗时<10毫秒),而无需等待网络响应(ajax耗时 > 50毫秒)。同时依赖后端接口对于列表和详情的处理须保持一致,即详情接口的字段只可能 ≥ 列表接口的字段。

这也是目前笔者使用的,代码大致如下:

goDetail () {
  const {activityId} = this
  this.$store.commit(‘AUCTION_DETAIL‘, this.$props)
  this.$router.push({
    name: ‘auctionroom-item‘,
    params: { activityId }})
}

OK,进入的逻辑兼顾了流畅的动画同时并行了数据的异步获取,那么新的问题又来了!我们需要考虑另一种情况:如果用户在列表页下翻了很多次,那么进入详情页再返回,定位得保持不变吧,怎么解决?

分页VS无限滚动

关于分页,最常见的2种模式就是页码分页或使用滚动条,这块在产品设计界也经常被拿出来讨论,找了2篇人人都是产品经理的文章,有兴趣的同学可以延伸阅读。

比较简单的结论可归纳为,页码则适用于那些用户在寻找特定信息的搜索结果列表页以及那些用户的浏览记录比较重要的场合,后者适用于向Twitter等那些用户重在消费无限的信息流而并不常搜寻特定的信息的应用,或者说前者多见于 PC 端,后者多见于 H5 。

如果是页码的模式,那么返回就不再是问题了,因为翻页信息通常会携带在页面url中,返回时我们只需要刷新当前页面的信息就可以了。问题这次的项目是后者,产品同学无法接受进入详情的回退让用户重新回顶部~

引入 keep-alive

要解决这个问题,需要使用 vue 的一个特性 keep-alive,使用原理:

  1. 包裹动态组件时会缓存组件实例,而不是销毁
  2. keep-alive 内路由切换时会调用 activated 和 deactivated 这两个钩子
  3. 套在 router-view 外面,受到影响的范围就是 router-view 里面的路由跳转。

注意事项:

  • 使用后会导致 created 可能不被调用,需要把一些逻辑移到 activated
  • 针对不需要保留状态的情况,可以在 deactivated 中调用 $destroy()

返回不刷新的问题解决了,但是产品同学又带来了新的问题,比如用户如果在详情页操作了!比如从订单列表进入详情后,更改了订单状态,那么列表页需要刷新这一条数据。

用 vuex 同样可以解决这个问题,在详情页的 deactivated 钩子更新列表中对应的该条数据,同样依赖后端对于详情和列表接口描述订单采用同样的数据格式,代码大致如下:

deactivated () {
  this.$store.commit(‘AUCTION_LIST_INDEX‘, this.index, this.$data)
}
最终效果如下:

对于订单这种,只有用户自己操作的数据,用这个方式就能满足需求。但对于商品、竞拍品这类,用户访问时间内有大量数据更新的情况,该方案其实不太完美~

我们或许需要在列表页中缓存当前可视区域的页码,可以使用 vue-scroller,然后在返回时刷新当前可视区域的数据。然而这就完了吗?你或许还是太天真!

无尽滚动的复杂度

做过 android、ios、react-native 开发的同学或许都知道大名鼎鼎的 ListView、ScrollView、RecyclerView

或许 Web 端一般没有类似的需求,但其实你也应该知道,DOM节点越多,内存占用越高。我们或许需要在可视区域内,复用列表节点,回收看不见的节点,但为了保持滚动条不因为内容回收导致的塌陷而变化, 还需要对他们做合并。

我就不做过多的拆解,掘金上有一篇来自 Google 大神的译文和一篇对应的引申:

  1. [译] 无尽滚动的复杂度 -- 来自 Google 大神的拆解
  2. 设计无限滚动下拉加载,实践高性能页面真谛

据文中观察,在真正产品线上使用这项技术的还比较少。可能是因为实现复杂度和收益比并不很高。但是,淘宝移动端检索页面实现了类似的思想。如下图,

总结

借用:当你想提供一个高性能的有良好用户体验的功能时,可能技术上一个简单的问题,就会演变成复杂问题的。这篇文章便是一个例证。随着 “Progressive Web Apps” 逐渐成为移动设备的一等公民(会吗?),高性能的良好体验会变得越来越重要。开发者也必须持续的研究使用一些模式来应对性能约束。这些设计的基础当然都是成熟的技术为根本。

我的看法:其实这种优化为什么不是浏览器去做!?

最后做个小广告,转转优品手机帮卖,让您高价卖掉自个的旧手机~ 如果对您有帮助,还请转发到朋友圈~

 如果你喜欢我们的文章,关注我们的公众号和我们互动吧。

原文地址:https://www.cnblogs.com/zhuanzhuanfe/p/8137503.html

时间: 2024-11-05 19:35:14

从列表到详情,没你想的那么简单的相关文章

线程池没你想的那么简单(续)

前言 前段时间写过一篇<线程池没你想的那么简单>,和大家一起撸了一个基本的线程池,具备: 线程池基本调度功能. 线程池自动扩容缩容. 队列缓存线程. 关闭线程池. 这些功能,最后也留下了三个待实现的 features . 执行带有返回值的线程. 异常处理怎么办? 所有任务执行完怎么通知我? 这次就实现这三个特性来看看 j.u.c 中的线程池是如何实现这些需求的. 再看本文之前,强烈建议先查看上文<线程池没你想的那么简单> 任务完成后的通知 大家在用线程池的时候或多或少都会有这样的需

桂电在线_微信公众平台开发之-运用angularjs显示学校公告新闻列表和详情页面

折腾angularjs的感悟 几天折腾,总的来说看了很多博客,要么不是最新的技术文档,要么写得不够完整,因为别人是基于他们的理解写的技术博客代码不一定会贴完整,所以一旦你用的是最新的想要看完整的实例就只能去官网查阅文档,现在通过实现下面的两个功能才真正了解什么是前端的MVVM框架. 另外,总结下我理解中的MVVM框架:后台php是MVC框架,一直做法是php处理数据,然后把数据渲染到模板,然后后台返回html页面给浏览器, 现在前端mvvm(model-view-viewmodel)就是:mod

zoj 3634 Bounty hunter(dp,没完全想清楚,需要回头再看_20151027)

M - Bounty hunter Time Limit:5000MS     Memory Limit:65536KB     64bit IO Format:%lld & %llu Submit Status Description Bounty hunter is a hero who always moves along cities to earn money by his power. One day he decides to N cities one by one At the

在美国当个程序员,没你想的那么难

想在美国当程序员?可能没你想象的那么难.就算没钱去正经大学念个计算机科学,也有其他出路. 最近,越来越多的在线学习编程学校在美国火了起来.许多真实的案例都为一些有志于此的人指出了一条光明大道:无论你是想找工作的穷学生,还是想改行的中年危机男,程序员的世界都欢迎你. "闪电战"编程课程 西雅图的"代码伙伴"(Code Fellows)学校,就非常确信他们能够帮助学生找到工作.他们为学生提供六个星期的"闪电战"编程课程,让学生能够完成从零基础达到基本

用产品思维设计API(三)——版本控制,没有你想的这么简单

用产品思维设计API(三)--版本控制,没有你想的这么简单 前言 最近公司内部在重构项目代码,包括API方向的重构,期间遇到了很多的问题,不由得让我重新思考了下. - 一个优雅的API该如何设计? - 前后端分离之后,API真的解耦分离了吗? - 不断的版本迭代,API的兼容性该如何做? ps.这里所说的API仅为Web API,提供APP\WEB开发使用. 年前,我司内部的接口已经进入了一个完全的重构阶段,参考了市面上各大平台的API和文档,自己也总结出了很多的心得.这里向大家分享一下,接下来

Activity 切换动画(小米图库列表进入详情页,图片从固定位置放大进入,缩小退出)

直接上效果图 ok,来分析下如何实现的吧 分析原理 首先确定,这是两个不同的 Activity,从图片列表页跳入到图片详情页:先来看进入详情页时的动画,从列表中所在 item 的位置一直放大到详情页的显示位置,这里我可以先告诉大家,当我们点击了这个 item 的时候,就已经启动了详情页,然后在详情页做相应的动画效果.既然是在详情页做动画效果,就需要在列表页把相应的值传过去,列表页 item 在屏幕上的位置,item 的大小,当然还有图片的资源,然后在详情页计算动画执行的参数.分析了进入动画,那么

Rxjava列表跳详情

//详情 public static final String XQURL="product/getProductDetail"; @GET(Api.XQURL) Flowable<XqBean> xqbean(@Query("pid") String pid ,@Query("resource") String resource); model public class XqModel implements IXqModel { @

scrapy案例:爬取翼蜂网络新闻列表和详情页面

# -*- coding: utf-8 -*- import scrapy from Demo.items import DemoItem class AbcSpider(scrapy.Spider): name = 'abcd' allowed_domains = ['www.cnyifeng.net'] # start_urls = ['http://abc.com/'] # 拼接url baseURL = "http://www.cnyifeng.net/news/1/{}.html&qu

管理系统列表和详情配置

简介: 通过配置json文件,直接生成列表和列表详情 配置文件是json格式 配置完成后可以生成预览 需要后端支持按一定的格式返回数据 config.json { "filterBar": { "isShow": true, "items": [{ "type": 1, "label": "", "field": "farmerName", &qu