带你深入Vue.js开发实战,从复杂列表的样式到性能优化

微信公众号:小武码码码


上一篇讲了 React.js 的复杂列表和性能优化,接下来我们谈一下我的老朋友–Vue吧,即Vue.js在开发关于复杂列表的开发和优化经验。

复杂列表作为前端开发中一个常见的需求场景,开发过程中需要考虑的因素很多,如何在保证功能实现的同时兼顾性能,是每个前端工程师都需要认真对待的问题。下面,那就让我们从复杂列表的样式、开发方式、高度自适应优化、性能优化以及和原生开发的区别这五个角度,来深入探讨一下这个问题。

一、复杂列表常见样式及使用场景

在实际的前端开发过程中,Vue的复杂列表的样式千变万化,但总体可以归纳为以下几种:

1. 瀑布流式列表

瀑布流列表像瀑布一样,一列一列依次排列,每列的高度不尽相同。当列表滚动到底部时,会自动加载更多数据,实现无限滚动效果。这种列表常用于图片展示、商品展示等场景,如花瓣网、淘宝商品列表等。

2. 树状结构列表

树状结构的列表每个节点可以展开和收起,展示层级关系。这种列表常用于文件目录、组织架构图等场景,如 GitHub 的代码目录结构。

3. 分组列表

分组列表将相关的内容划分到一个组中,每个组有一个组标题。这种列表常用于通讯录、城市列表等场景,如微信的通讯录。

4. 可拖拽排序列表

可拖拽排序列表允许用户通过拖拽对列表项进行排序。这种列表常用于任务列表、播放列表等场景,如 Jira 中的任务列表。

5. 虚拟滚动列表

虚拟滚动列表通过只渲染可视区域的列表项,在滚动时动态更新可视区域,从而达到优化性能的目的。这种列表常用于渲染大量数据的场景,如微博的信息流。

二、复杂列表的几种开发方式

针对以上复杂列表,在 Vue.js 开发中,一般有以下几种开发方式:

1. 使用 v-for 指令

v-for 指令是 Vue.js 提供的列表渲染指令,可以将一个数组渲染为列表。使用 v-for 时,要为每个列表项提供一个唯一的 key 属性。示例代码如下:

<template>
  <ul>
    <li v-for="item in list" :key="item.id">
      {{ item.title }}
    </li>  
  </ul>
</template>

<script>
export default {
  data() {
    return {
      list: [
        { id: 1, title: '标题1' },
        { id: 2, title: '标题2' },
        { id: 3, title: '标题3' }
      ]
    }
  }
}
</script>

2. 使用组件递归

对于树状结构的列表,可以定义一个组件,在组件内部递归调用自身,从而实现树的展示。示例代码如下:

<!-- Tree.vue -->
<template>
  <ul>
    <li v-for="item in treeData" :key="item.id">
      <span>{{ item.title }}</span>
      <Tree v-if="item.children" :treeData="item.children"/>    
    </li>
  </ul>  
</template>

<script>
export default {
  name: 'Tree',
  props: ['treeData']
}
</script>

3. 使用计算属性

对于需要对列表数据进行转换、过滤、排序等操作的情况,可以使用计算属性。在计算属性中对数据进行处理,然后在模板中使用处理后的数据。示例代码如下:

<template>
  <ul>
    <li v-for="item in sortedList" :key="item.id">
      {{ item.title }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      list: [
        { id: 1, title: '标题3' },
        { id: 2, title: '标题1' },  
        { id: 3, title: '标题2' }
      ]
    }
  },
  computed: {
    sortedList() {
      return this.list.sort((a, b) => a.id - b.id)
    }
  }
}
</script>

4. 使用第三方组件库

对于一些复杂的列表需求,可以使用第三方组件库来加快开发速度,如 Element UI、iView 等。这些组件库提供了现成的列表组件和 API,可以满足大部分场景的需求。

以上就是 Vue.js 中几种常见的列表开发方式,在实际开发中需要根据具体需求选择合适的方式。

三、高度自适应列表的实现和优化

对于高度不固定的列表,如瀑布流列表,需要实现列表项的高度自适应。下面介绍几种常见的实现和优化方式:

1. 利用 flex 布局实现自适应

利用 CSS3 的 flex 布局,可以轻松实现列表的自适应布局。关键CSS代码如下:

.list {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}
.list-item {  
  width: 30%;
  margin-bottom: 20px;
}

在这个例子中,列表设置为 display: flexflex-wrap: wrap,实现多列布局;列表项设置 width 为固定值,就可以根据内容自适应高度。

2. 使用 Grid 布局实现自适应

与 flex 类似,CSS3 的 Grid 布局也可以实现列表的自适应布局。关键CSS代码如下:

.list {
  display: grid;  
  grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
  grid-gap: 20px;  
}

在这个例子中,列表设置为 display: grid,grid-template-columns 属性定义了列的数量和宽度,repeat(auto-fill, minmax(300px, 1fr)) 表示列的宽度至少为 300px,自动填充,实现响应式布局;grid-gap 定义列表项之间的间距。

3.动态计算列表项高度

对于列表项高度不固定的情况,可以通过动态计算列表项的高度来实现自适应。具体思路如下:

  1. 在列表渲染完成后,通过 $refs 获取到所有列表项的 DOM 元素。
  2. 通过 getBoundingClientRect() 方法获取每个列表项的高度。
  3. 找出所有列表项中的最大高度,设置为列表容器的高度。

关键代码如下:

<template>
  <div class="list" :style="{ height: listHeight + 'px' }">
    <div class="list-item" v-for="item in list" :key="item.id" :ref="setItemRef">
      <!-- 列表项内容 -->
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      listHeight: 0,
      itemRefs: []
    }
  },
  mounted() {
    this.$nextTick(() => {
      const heights = this.itemRefs.map(item => item.getBoundingClientRect().height)
      this.listHeight = Math.max(...heights)
    })
  },
  methods: {
    setItemRef(el) {
      if (el) {
        this.itemRefs.push(el)
      }
    }
  }
}
</script>

在这个例子中,我们定义了一个 listHeight 变量来存储列表容器的高度,并将其绑定到容器的 height 样式上。在 mounted 生命周期中,我们通过 $nextTick 在 DOM 更新后获取所有列表项的高度,并将最大高度设置给 listHeight

4. 使用虚拟滚动优化长列表渲染

对于数据量较大的长列表,如果直接渲染所有数据,会导致页面卡顿。这时可以使用虚拟滚动技术进行优化,只渲染可视区域内的列表项。

虚拟滚动的基本原理是:

  1. 计算可视区域内可以渲染多少个列表项。
  2. 监听滚动事件,根据滚动位置动态更新列表项。
  3. 将列表项的渲染范围限定在可视区域内,其他部分用空白占位。

在 Vue.js 中,可以使用第三方的虚拟滚动组件,如 vue-virtual-scrollervue-virtual-scroll-list 等。以 vue-virtual-scroller 为例,使用方式如下:

<template>
  <virtual-scroller class="scroller" :items="list" item-height="100">
    <template v-slot="{ item }">
      <!-- 列表项内容 -->
    </template>
  </virtual-scroller>
</template>

<script>
import { VirtualScroller } from 'vue-virtual-scroller'
import 'vue-virtual-scroller/dist/vue-virtual-scroller.css'

export default {
  components: {
    VirtualScroller
  }
}
</script>

<style scoped>
.scroller {
  height: 500px;
}  
</style>

在这个例子中,我们引入了 vue-virtual-scroller 组件,将列表数据传递给 items 属性,item-height 属性指定了列表项的高度。列表内容通过 v-slot 渲染。最后,我们设置了容器的固定高度,实现滚动效果。

5. 优化列表项组件

对于复杂的列表项内容,可以将其封装为单独的组件,然后在列表中引用。这样可以提高代码的可维护性和可复用性。同时,在列表项组件中,我们可以对一些耗时的操作进行优化,如:

  • 对于需要进行大量计算的属性,可以使用 computed 进行缓存。
  • 对于需要频繁操作 DOM 的地方,可以使用 v-show 代替 v-if,减少 DOM 的添加和移除。
  • 对于一些事件监听器,可以在组件销毁时手动移除,防止内存泄漏。

示例代码如下:

<template>
  <div class="list-item">
    <div v-if="isLoading" class="loading">加载中...</div>
    <div v-show="!isLoading">
      <h3>{{ itemData.title }}</h3>
      <p>{{ itemData.content }}</p>
      <button @click="handleClick">点击</button>
    </div>
  </div>
</template>

<script>
export default {
  props: ['itemData'],
  data() {
    return {
      isLoading: false
    }
  },
  computed: {
    // 对需要计算的属性进行缓存
    formattedContent() {
      return this.itemData.content.replace(/\n/g, '<br>')
    }
  },
  methods: {
    handleClick() {
      this.isLoading = true
      // 模拟异步操作
      setTimeout(() => {
        this.isLoading = false
      }, 1000)
    }
  },
  beforeDestroy() {
    // 移除事件监听器
    document.removeEventListener('click', this.handleDocumentClick)
  }  
}
</script>

四、复杂列表的性能优化

对于复杂的列表,性能优化至关重要。以下从多方面介绍如何进行性能调优:

1. 长列表性能优化

前面已经提到,对于长列表可以使用虚拟滚动技术避免大量 DOM 元素的渲染。除此之外,还可以采用以下几个优化策略:

  • 避免在列表中使用 v-if,而是用 v-show 代替。因为 v-if 会频繁地添加和移除 DOM,而 v-show 只是切换 display 属性。
  • 使用函数式组件。函数式组件没有状态和实例,渲染开销较小。
  • 开启列表项组件的缓存。可以用 keep-alive 组件将列表项缓存起来,避免重复渲染。
  • 通过 Object.freeze 冻结列表数据,避免 Vue 做不必要的响应式跟踪。

2. 图片懒加载

在列表中经常会遇到大量图片的情况。如果一次性加载所有图片,会占用大量网络资源,从而影响页面性能。这时可以使用图片懒加载技术,只加载可视区域内的图片。

可以使用第三方的懒加载组件,如 vue-lazyload。示例代码如下:

<template>
  <ul>
    <li v-for="item in list" :key="item.id">
      <img v-lazy="item.imgUrl" />
    </li>
  </ul>
</template>

<script>
  import VueLazyload from "vue-lazyload";

  export default {
    directives: {
      lazy: VueLazyload.directive,
    },
  };
</script>

在这个例子中,我们注册了 vue-lazyload 指令,将需要懒加载的图片 URL 设置给 v-lazy 指令。当图片进入可视区域时,才会触发图片的加载。

3. 列表项 DOM 复用

在某些列表中,列表项的 DOM 结构较为复杂,如果频繁地创建和销毁 DOM,会影响性能。这时可以考虑 DOM 复用的方式,也就是在列表项变化时,尽可能地复用已有的 DOM 元素。

Vue.js 内部已经帮我们做了很多 DOM 复用的工作,主要是通过 v-for 指令的 :key 属性来实现。当 key 值相同时,Vue.js 会认为是同一个元素,并尽可能地复用。因此,合理设置 key 的值非常重要,应该使用稳定的、唯一的值,如 idindex 等。

4. 使用 Object.freeze 优化不可变数据

在一些场景下,列表数据在渲染后不会发生变化,这时我们可以使用 Object.freeze 冻结数据。冻结后的数据不能被修改,Vue.js 会跳过这些数据的响应式跟踪,从而提升性能。示例代码如下:

export default {
  data() {
    return {
      list: []
    }
  },
  async created() {
    const data = await fetchData()
    this.list = Object.freeze(data)
  }
}

5. 使用 Intersection Observer 实现曝光埋点

在复杂列表中,经常需要做一些曝光统计,即统计列表项的可见情况。在过去,这种需求通过监听 scroll 事件,比较元素的位置来实现,这样会频繁地触发事件和计算,容易引起性能问题。

现在,我们可以使用浏览器提供的 Intersection Observer API 来实现曝光统计。它可以检测目标元素与视口的交叉情况,当元素可见时触发回调。示例代码如下:

<template>
  <ul>
    <li v-for="item in list" :key="item.id" ref="items">
      <!-- 列表项内容 -->
    </li>
  </ul>  
</template>

<script>
export default {
  data() {
    return {
      observer: null
    }
  },
  mounted() {
    this.observer = new IntersectionObserver(entries => {
      entries.forEach(entry => {
        if (entry.isIntersecting) {
          // 列表项可见,记录曝光
          console.log('列表项可见:', entry.target)
        }
      })
    })

    this.$refs.items.forEach(item => {
      this.observer.observe(item)
    })
  },
  beforeDestroy() {
    this.observer.disconnect()
  }
}
</script>

在这个例子中,我们在 mounted 生命周期创建了一个 IntersectionObserver 实例,并观察所有列表项。当列表项可见时,就会触发回调,此时可以记录曝光。最后,在组件销毁时,记得调用 disconnect 方法断开观察。

五、Vue.js 和原生实现复杂列表的对比

最后,让我们来对比一下 Vue.js 和原生 JavaScript 在实现复杂列表方面的异同。

1. 开发效率

在开发效率方面,Vue.js 明显占优。它提供了声明式的模板语法和丰富的指令,可以让我们更加专注于业务逻辑的开发,而不必过多关注底层的 DOM 操作。相比之下,原生 JavaScript 需要手动操作 DOM,代码量更大,开发效率更低。

2. 性能

在性能方面,原生 JavaScript 可以更加精细地控制 DOM 操作,有更大的优化空间。但 Vue.js 也提供了诸多性能优化策略,如 v-for 的 :key、函数式组件、Object.freeze 等。在合理使用的情况下,Vue.js 的性能也能达到较高的水平。

3. 可维护性

在可维护性方面,Vue.js 更有优势。Vue.js 采用组件化的开发模式,可以将复杂的列表拆分成多个小组件,每个组件负责自己的逻辑和视图。这种模式更加模块化和可复用,代码更加清晰和易于维护。而原生 JavaScript 的代码通常会集中在一起,逻辑和视图混杂,较难维护。

4. 学习成本

在学习成本方面,原生 JavaScript 显然更低。它只需要掌握基本的 DOM 操作和事件处理即可。而 Vue.js 除了 JavaScript 基础,还需要学习它的专有语法和 API,相对来说学习成本更高。

5. 灵活性

在灵活性方面,原生 JavaScript 更占优势。因为它可以完全控制页面的每一个细节,不受任何框架的限制。而 Vue.js 必须遵守它的一些规则和约定,在某些场景下可能不够灵活。

综上所述,Vue.js 和原生 JavaScript 各有优劣。在实际开发中,需要根据具体情况选择合适的技术栈。对于中小型项目、对开发效率要求较高的场景,Vue.js 是更好的选择;而对于对性能和灵活性有极高要求的场景,原生 JavaScript 可能更合适。

六、总结

本文从复杂列表的样式、开发方式、自适应优化、性能优化以及与原生实现的对比等方面,全面探讨了 Vue.js 开发复杂列表的问题。归纳起来,主要有以下几点:

  1. 常见的复杂列表样式有瀑布流、树状结构、分组、拖拽排序、虚拟滚动等,应根据具体需求选择合适的样式。
  2. Vue.js 实现复杂列表的方式有直接使用 v-for、组件递归、计算属性、第三方组件库等,同样应视情况而定。
  3. 关于列表的自适应优化,可以使用 flex 或 Grid 布局、动态计算高度、使用 scroller 组件等方式实现。
  4. 关于列表的性能优化,主要思路有虚拟滚动、图片懒加载、列表项组件优化、DOM 复用、数据冻结、Intersection Observer 等。
  5. Vue.js 和原生 JavaScript 实现复杂列表各有优劣,Vue.js 在开发效率和可维护性方面更有优势,而原生 JavaScript 在性能和灵活性方面更胜一筹。
  6. 需要指出的是,文中介绍的优化技巧并非放之四海而皆准,在实际开发中还需要因地制宜、灵活应用。一方面要积极尝试各种优化策略,另一方面也要通过性能工具如 Chrome devtools 来评估优化效果,避免过度优化。此外,除了本文提到的方面,还需要关注代码层面的优化,如减少重复计算、缓存频繁使用的数据等。

七、展望

通过上文的分析和总结,我们对 Vue.js 开发复杂列表有了较为全面的认识。但前端技术日新月异,Vue.js 也在不断迭代更新,未来在复杂列表的开发方面一定还会涌现出更多的思路和方案。

1. Composition API

Vue.js 3.0 引入了 Composition API,它为逻辑复用和代码组织提供了更好的支持。在复杂列表的开发中,我们可以使用 Composition API 将一些通用的逻辑(如列表项的曝光监听)抽离出来,做到真正的”write once, use anywhere”。同时,Composition API 也让列表组件的代码结构更加扁平和清晰。

2. Serverless Rendering

目前 Vue.js 主要使用客户端渲染(CSR)和服务端渲染(SSR)两种模式。但随着 Serverless 技术的兴起,Serverless Rendering 也成为了一种新的选择。它结合了 CSR 和 SSR 的优点,可以在请求时动态地在云端生成页面,既有良好的首屏性能,又有 CSR 的交互体验。在列表页这种数据量较大的场景下,Serverless Rendering 的优势将更加明显。

3. Web Components

Web Components 是一套基于浏览器的原生组件化方案,它允许开发者创建可复用的自定义元素。与 Vue.js 等前端框架不同,Web Components 不依赖任何外部库,可以跨框架使用。如果你的列表组件需要嵌入到其他框架的项目中,你可以考虑使用 Web Components 来封装,提高其通用性。当然,目前 Web Components 的生态还不够成熟,在使用时需要权衡利弊。

4. 微前端

随着前端项目变得越来越庞大和复杂,微前端架构开始受到关注。它将一个大型前端应用拆分成多个独立的子应用,每个子应用可以独立开发、测试和部署。在这种架构下,复杂列表可能会被拆分到不同的子应用中,这对列表的开发和优化提出了新的挑战,如子应用间的通信、数据同步等。因此,在微前端环境下开发复杂列表,需要有更全局的视角和更完善的设计。

八、参考资料

  1. Vue.js 官方文档 – 列表渲染
  2. Vue.js 官方文档 – 函数式组件
  3. Vue Virtual Scroller – 基于 Vue.js 的虚拟滚动组件
  4. vue-lazy-hydration – 基于 Intersection Observer 的组件懒加载方案
  5. 利用 Intersection Observer 优化页面性能的4个小 tips
  6. 浅谈 Vue 中长列表的优化方法
  7. 再谈 Vue 虚拟列表的实现原理及其在实战中的应用
  8. 微前端在美团外卖的实践
  9. Web Components 入门实例教程
  10. 精读《Vue3.0 Function API》
  11. 基于 Vue 的前端架构,我做了这 15 点

以上资料从不同角度介绍了 Vue.js 开发复杂列表的实践经验和优化策略,对于进一步探索这一话题大有裨益。需要强调的是,学习他人的经验固然重要,但更重要的是在自己的项目中勇于尝试和创新。只有将前人的智慧内化为自己的知识,并在实践中不断迭代,才能真正掌握复杂列表的开发之道,成为一名优秀的前端工程师。

让我们携手并进,一起探索 Vue.js 开发复杂列表的奥秘,为更好的用户体验和开发体验而不懈努力!

 

原文链接:https://juejin.cn/post/7363519735056072730 作者:小武部长码码码

(0)
上一篇 2024年4月23日 上午9:51
下一篇 2024年5月13日 上午9:29

相关推荐

发表回复

登录后才能评论