前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >15 v-if 条件渲染与 v-for 列表渲染

15 v-if 条件渲染与 v-for 列表渲染

作者头像
LIYI
发布2020-02-13 11:54:19
1.8K0
发布2020-02-13 11:54:19
举报
文章被收录于专栏:艺述论专栏艺述论专栏
代码语言:javascript
复制
目录

v-if 条件渲染
组件的缓存和复用
v-for 与大数据列表中的组件复用
源码

v-if 条件渲染

vue源码中有这样一个函数:

代码语言:javascript
复制
function processIf (el) {
  var exp = getAndRemoveAttr(el, 'v-if');
  if (exp) {
    el.if = exp;
    addIfCondition(el, {
      exp: exp,
      block: el
    });
  } else {
    if (getAndRemoveAttr(el, 'v-else') != null) {
      el.else = true;
    }
    var elseif = getAndRemoveAttr(el, 'v-else-if');
    if (elseif) {
      el.elseif = elseif;
    }
  }
}

它是专门处理v-if条件编译的。

v-if 指令用于条件性地渲染一块内容。如下所示,当且仅当showtrue时,p标签才会被创建并渲染:

代码语言:javascript
复制
<p @click="show=false" v-if="show">{{message}}</p>

v-if搭配一起使用的是v-elsev-else-if。但是没有v-end

从上面的 vue 源码也可以看出,vue处理的是单个的节点属性,并没有考虑上下文之间的语法关系。这决定了v-if不能独立存在,必须附属在一个元素上。

如果v-if不方便添加在元素上怎么办?

举个例子,例如:

代码语言:javascript
复制
  <h1 v-if="show">Title</h1>
  <p v-if="show">Paragraph 1</p>
  <p v-if="show">Paragraph 2</p>

这种情况下需要添加多个v-if,比较麻烦。

或者我们可以使用一个div包装一下:

代码语言:javascript
复制
<div v-if="show">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</div>

但如果此处如果不方便添加或者我们不想添加div的话,vue提供了一个不可见的元素标签template,可以解决这个问题:

代码语言:javascript
复制
<template v-if="show">
  <h1>Title</h1>
  <p>Paragraph 1</p>
  <p>Paragraph 2</p>
</template>

那么template是怎么实现的呢?

通过查看vue源码发现,如果标签(tag)是template,直接处理子元素或者返回了void 0

所以,template是非可见元素,在vuetemplate仅是为了方便处理群组关系而存在的。

组件的缓存和复用

另处,值得一提的是,v-if是条件渲染,只有条件为true,组件才会创建;而另一个具有同样效果的指令v-show,仅是改变组件的display样式,无论显示与否,始终都会创建。

这个特征决定了v-if可以复用已经创建过的元素。例如:

代码语言:javascript
复制
<!-- 组件缓存 -->
<template v-if="loginType === 'username'">
  <label>用户名</label>
  <input placeholder="Enter your username">
</template>
<template v-else>
  <label>邮箱</label>
  <input placeholder="Enter your email address">
</template>
<button @click="loginType = loginType === 'username'?'':'username'">切换登陆类型</button>

运行效果:

明明是两个逻辑分支,为什么上一个分支里的组件输入了123,保留到了下一个分支的组件里?v-if的机制,不是每次都重新创建组件的吗?

因为vue内部为提高视图的渲染效率,减少组件在运行时创建的开销,采用了复用机制。

其中,从源码看判断两个组件是否相同的代码是这样的:

代码语言:javascript
复制
function isSameChild (child, oldChild) {
  return oldChild.key === child.key && oldChild.tag === child.tag
}

tag相同,且key相同,vue才认为是相同的组件。为了避免不同组件在渲染时受缓存的影响,所以vue规定组件应该有且只准有一个唯一的key,特别在v-for列表中。

理解了原理,修改起来就简单了。对于上面的受影响的组件,只需要修改为:

代码语言:javascript
复制
<!-- 组件缓存 -->
<template v-if="loginType === 'username'">
  <label>用户名</label>
  <input placeholder="Enter your username" key="username-input">
</template>
<template v-else>
  <label>邮箱</label>
  <input placeholder="Enter your email address" key="email-input">
</template>

在这里有一个问题,为什么input的值会被保留,但是label的文本却会变化呢?

这是编译时与运行时的些微差别。在这里label标签组件仍然会被复用,但是在视图渲染的过程中,新的文本内容会被赋值过来,因为它是在编译阶段就被定义的。

v-for 与大数据列表中的组件复用

v-for指令用于渲染一个列表。被重复渲染的元素要求有一个key。这个key一般取元素数据中的某个唯一的字段,id或者其它字段。如果没有,可以使用index,即列表本身的索引代替:

代码语言:javascript
复制
<!-- for -->
<ol>
  <li v-for="(todo,index) in todos" :key="index">{{ todo.text }}</li>
</ol>

假设数据列表很大,有几千条。这么多数据一般也不会在页面上全部显示,通常的做法是放在一个滚动容器内,只显示最新的 10 条或 8 条。

对于这样的大数据列表,如果优化它的渲染效率呢?

在这里可以利用key做文章。仅使可见的组件元素享用唯一的key,不可见的元素用一个简单的占位符代替。

为了实践这个想法,作者写了一个示例。模板代码为:

代码语言:javascript
复制
<template>
  <div>
    <!-- 大数据列表 -->
    <div class="list" ref="list" @scroll="onScroll">
      <ol>
        <li
          v-if="showItem(index)"
          v-for="(todo,index) in todos"
          :key="index%11"
        >{{ todo.text }} - {{index%11}}</li>
        <li v-else></li>
      </ol>
    </div>
  </div>
</template>

设置滚动区域高度为 300px,每个元素高度为 30px,滚动框内最多容纳10个元素。但是key的值并不是index%10,而是index%11,这是为了让底部多一个元素,避免滚动时出现缝隙。

只有显示的元素才展示数据,不显示的元素以空白的li代替。

主要的 js 代码为:

代码语言:javascript
复制
<script>
const ITEM_HEIGHT = 30;
const LIST_HEIGHT = ITEM_HEIGHT * 11;

export default {
  ...
  mounted() {
    for (let j = 0; j < 2000; j++) this.todos.push({ text: "元素内容" + j });
  },
  methods: {
    onScroll() {
      this.currentScrollTop = this.$refs.list.scrollTop;
    },
    showItem(index) {
      let startPos = Math.floor( this.currentScrollTop / ITEM_HEIGHT )
      let endPos = startPos + 10
      return index >= startPos && index <= endPos
    }
  }
};
</script>

showItem是关键,它决定了当前的元素是否显示。布尔值是通过滚动区域的scrollTop属性计算出来的。

运行效果:

可以看到,一共 2000 条数据,也只有中间 11 条数据是真正渲染的。如果组件元素是复杂的,所有许多业务逻辑,这种做法可以显著提高渲染效率。

但是这个方案还有改进的空间。就是在滚动的div上,自定义实现一个滚动条。这样就不再依赖于空白的li作为占位符了。如果实现这一步,列表里只需要渲染 11 个元素组件。数据再大,渲染也没有问题。

事实上,苹果 iOS UIKit 的表格组件就是这样实现的。

源码

https://git.code.tencent.com/shiqiaomarong/vue-go-rapiddev-example/tags/v20200110

参考链接

  • https://cn.vuejs.org/v2/guide/list.html
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 艺述论 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • v-if 条件渲染
  • 组件的缓存和复用
  • v-for 与大数据列表中的组件复用
  • 源码
  • 参考链接
相关产品与服务
大数据
全栈大数据产品,面向海量数据场景,帮助您 “智理无数,心中有数”!
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档