MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Vue模板语法 条件渲染与列表渲染的最佳实践

2023-07-166.9k 阅读

Vue 模板语法中的条件渲染

条件渲染基础:v-if 指令

在 Vue 开发中,v-if 指令是实现条件渲染的重要手段。它基于表达式的真假来决定是否渲染一个元素或组件。例如,我们有一个简单的布尔变量 isShow,用于控制一个按钮的显示与否:

<template>
  <div>
    <button v-if="isShow">点击我</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: true
    };
  }
};
</script>

在上述代码中,由于 isShow 初始值为 true,按钮会被渲染到页面上。如果将 isShow 设置为 false,按钮将不会出现在 DOM 中。

v-if 指令也可以应用在 <template> 标签上,这在需要控制多个元素的显示隐藏时非常有用。比如我们有一个包含多个元素的小组件:

<template>
  <div>
    <template v-if="isShow">
      <h3>标题</h3>
      <p>这是一段描述性文字。</p>
      <button>操作按钮</button>
    </template>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: true
    };
  }
};
</script>

isShowtrue 时,<template> 内的所有元素都会被渲染;反之则不会出现在 DOM 中。

v-else 和 v-else-if

在实际开发中,我们经常需要根据不同的条件展示不同的内容,这时候 v-elsev-else-if 就派上用场了。v-else 必须紧跟在 v-ifv-else-if 元素之后,不能单独使用。例如,我们根据用户的登录状态展示不同的内容:

<template>
  <div>
    <div v-if="isLoggedIn">
      <p>欢迎,{{ username }}</p>
      <button @click="logout">退出登录</button>
    </div>
    <div v-else>
      <button @click="login">登录</button>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isLoggedIn: false,
      username: 'John'
    };
  },
  methods: {
    login() {
      this.isLoggedIn = true;
    },
    logout() {
      this.isLoggedIn = false;
    }
  }
};
</script>

在上述代码中,当 isLoggedIntrue 时,显示欢迎信息和退出登录按钮;当 isLoggedInfalse 时,显示登录按钮。

v-else-if 则用于添加多个条件判断。比如我们根据用户的年龄来显示不同的提示信息:

<template>
  <div>
    <div v-if="age < 18">
      <p>你还未成年哦。</p>
    </div>
    <div v-else-if="age >= 18 && age < 60">
      <p>你已成年,可以进行相关操作。</p>
    </div>
    <div v-else>
      <p>你已步入老年,请注意身体。</p>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      age: 25
    };
  }
};
</script>

这里根据 age 的值,依次判断并渲染相应的内容。

条件渲染的性能考量

v-if 是“真正”的条件渲染,因为它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建。这意味着当条件块内有复杂的逻辑或组件时,v-if 的切换开销较大。

例如,假设我们有一个包含大量计算属性和方法的组件:

<template>
  <div v-if="isShow">
    <ComplexComponent />
  </div>
</template>

<script>
import ComplexComponent from './ComplexComponent.vue';

export default {
  components: {
    ComplexComponent
  },
  data() {
    return {
      isShow: true
    };
  }
};
</script>

每次 isShow 的值发生变化,ComplexComponent 都会被销毁和重建,这在性能上是有一定消耗的。

与之相对的是 v-showv-show 只是简单地切换元素的 CSS display 属性。例如:

<template>
  <div v-show="isShow">
    <p>这段文字通过 v-show 控制显示隐藏。</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      isShow: true
    };
  }
};
</script>

即使 isShowfalse,该元素仍然存在于 DOM 中,只是被隐藏了。所以 v-show 的初始渲染开销较大,但切换开销较小。

一般来说,如果条件在运行时很少改变,使用 v-if 更合适;如果条件需要频繁切换,使用 v-show 更能提升性能。

Vue 模板语法中的列表渲染

列表渲染基础:v-for 指令

v-for 指令用于基于一个数组来渲染一个列表。它的基本语法是 v-for="(item, index) in items",其中 items 是数据源数组,item 是数组中的每一项,index 是当前项的索引(可选)。

例如,我们有一个水果列表,要在页面上展示出来:

<template>
  <ul>
    <li v-for="(fruit, index) in fruits" :key="index">
      {{ index + 1 }}. {{ fruit }}
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      fruits: ['苹果', '香蕉', '橙子']
    };
  }
};
</script>

在上述代码中,v-for 遍历 fruits 数组,为每个水果生成一个 <li> 元素,并显示其序号和名称。

需要注意的是,:key 是一个很重要的属性。Vue 会使用 key 来跟踪每个节点的身份,从而高效地更新和重新排序现有元素。当列表中的项目顺序发生改变或者有新的项目添加时,如果没有正确设置 key,Vue 可能会进行不必要的 DOM 操作,导致性能问题。通常,我们会使用数组项的唯一标识作为 key,如果没有唯一标识,也可以使用索引,但这在一些复杂场景下可能会有问题。

遍历对象

v-for 不仅可以遍历数组,还可以遍历对象。语法为 v-for="(value, key, index) in object",其中 value 是对象的属性值,key 是属性名,index 是索引(可选)。

例如,我们有一个用户信息对象:

<template>
  <div>
    <p v-for="(value, key) in user" :key="key">
      {{ key }}: {{ value }}
    </p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      user: {
        name: 'Alice',
        age: 28,
        email: 'alice@example.com'
      }
    };
  }
};
</script>

这样就可以将用户对象的属性和值一一展示出来。

在组件中使用 v-for

在组件中使用 v-for 时,需要注意传递数据和事件处理。假设我们有一个简单的待办事项组件 TodoItem,并且有一个待办事项列表:

<template>
  <div>
    <TodoItem v-for="(todo, index) in todos" :key="index" :todo="todo" @delete-todo="deleteTodo" />
  </div>
</template>

<script>
import TodoItem from './TodoItem.vue';

export default {
  components: {
    TodoItem
  },
  data() {
    return {
      todos: [
        { id: 1, text: '学习 Vue', completed: false },
        { id: 2, text: '完成项目', completed: false }
      ]
    };
  },
  methods: {
    deleteTodo(todoId) {
      this.todos = this.todos.filter(todo => todo.id!== todoId);
    }
  }
};
</script>

TodoItem 组件中:

<template>
  <li>
    {{ todo.text }}
    <input type="checkbox" :checked="todo.completed" @change="toggleCompleted" />
    <button @click="deleteTodo">删除</button>
  </li>
</template>

<script>
export default {
  props: {
    todo: {
      type: Object,
      required: true
    }
  },
  methods: {
    toggleCompleted() {
      this.$emit('toggle-completed', this.todo.id);
    },
    deleteTodo() {
      this.$emit('delete-todo', this.todo.id);
    }
  }
};
</script>

这里通过 v-fortodos 列表中的每一项传递给 TodoItem 组件,并监听 delete - todo 事件来删除相应的待办事项。

列表渲染的性能优化

当处理大量数据的列表渲染时,性能优化就变得尤为重要。一种常见的优化方式是使用虚拟滚动。Vue 有一些第三方库,如 vue - virtual - scroll - list,可以帮助我们实现虚拟滚动。

vue - virtual - scroll - list 为例,首先安装该库:

npm install vue - virtual - scroll - list

然后在组件中使用:

<template>
  <div>
    <VirtualList
      :data-key="'id'"
      :buffer-size="10"
      :list="bigList"
      :item-size="50"
      key-field="id"
    >
      <template v - slot="{ item }">
        <div>{{ item.text }}</div>
      </template>
    </VirtualList>
  </div>
</template>

<script>
import { VirtualList } from 'vue - virtual - scroll - list';
import 'vue - virtual - scroll - list/dist/vue - virtual - scroll - list.css';

export default {
  components: {
    VirtualList
  },
  data() {
    const bigList = [];
    for (let i = 0; i < 10000; i++) {
      bigList.push({ id: i, text: `第 ${i + 1} 项` });
    }
    return {
      bigList
    };
  }
};
</script>

在上述代码中,VirtualList 组件只会渲染当前可见区域的列表项,大大提高了性能。buffer - size 表示预渲染的额外项数,item - size 表示每个列表项的高度(如果是固定高度)。通过这种方式,即使处理上万条数据的列表,也不会出现性能问题。

条件渲染与列表渲染的结合使用

在列表中进行条件渲染

在实际项目中,我们经常需要在列表渲染的基础上进行条件渲染。比如,我们有一个商品列表,要根据商品的库存状态显示不同的样式。

<template>
  <ul>
    <li v - for="(product, index) in products" :key="index">
      <span :class="{ 'out - of - stock': product.stock === 0 }">{{ product.name }}</span>
      <p v - if="product.stock > 0">库存: {{ product.stock }}</p>
      <p v - else>该商品已售罄。</p>
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      products: [
        { id: 1, name: '商品 A', stock: 5 },
        { id: 2, name: '商品 B', stock: 0 }
      ]
    };
  }
};
</script>

在上述代码中,我们通过 v - for 渲染商品列表,然后针对每个商品,根据其库存 stock 的值,使用 v - ifv - else 显示不同的库存信息,并通过 :class 绑定不同的样式类,当库存为 0 时,添加 out - of - stock 类,用于设置商品售罄的样式。

根据条件渲染不同的列表

有时候,我们需要根据不同的条件渲染不同的列表。例如,我们有一个用户管理系统,根据用户的角色不同,显示不同的用户列表。

<template>
  <div>
    <div v - if="role === 'admin'">
      <h3>管理员用户列表</h3>
      <ul>
        <li v - for="(admin, index) in adminUsers" :key="index">{{ admin.name }}</li>
      </ul>
    </div>
    <div v - else>
      <h3>普通用户列表</h3>
      <ul>
        <li v - for="(user, index) in normalUsers" :key="index">{{ user.name }}</li>
      </ul>
    </div>
  </div>
</template>

<script>
export default {
  data() {
    return {
      role: 'admin',
      adminUsers: [
        { name: 'Admin1' },
        { name: 'Admin2' }
      ],
      normalUsers: [
        { name: 'User1' },
        { name: 'User2' }
      ]
    };
  }
};
</script>

这里根据 role 的值,使用 v - ifv - else 来决定渲染管理员用户列表还是普通用户列表。

条件渲染与列表渲染结合的性能注意点

当条件渲染与列表渲染结合使用时,要特别注意性能问题。例如,如果在一个大列表中频繁地进行条件渲染切换,可能会导致大量的 DOM 操作,影响性能。

以之前商品列表的例子为例,如果库存状态频繁变化,导致 v - ifv - else 频繁切换,会使得 DOM 元素不断地创建和销毁。在这种情况下,可以考虑使用 v - show 代替 v - if,减少 DOM 操作的开销。

另外,在结合使用时,也要确保 v - for 中的 key 属性设置正确,避免因 key 不正确导致的性能问题。同时,如果列表数据量较大且条件切换频繁,还可以考虑采用一些优化策略,如局部更新、虚拟 DOM 差异算法的合理利用等,以提升整体性能。

在处理复杂业务逻辑时,可能会出现多个条件嵌套在列表渲染中的情况。比如,我们不仅要根据库存判断显示内容,还要根据商品的类别显示不同的副标题。

<template>
  <ul>
    <li v - for="(product, index) in products" :key="index">
      <h3>{{ product.name }}</h3>
      <template v - if="product.category === 'electronics'">
        <p>这是电子产品类商品。</p>
      </template>
      <template v - else - if="product.category === 'clothes'">
        <p>这是服装类商品。</p>
      </template>
      <template v - else>
        <p>其他类商品。</p>
      </template>
      <p v - if="product.stock > 0">库存: {{ product.stock }}</p>
      <p v - else>该商品已售罄。</p>
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      products: [
        { id: 1, name: '手机', category: 'electronics', stock: 10 },
        { id: 2, name: 'T恤', category: 'clothes', stock: 0 }
      ]
    };
  }
};
</script>

在这种情况下,要清晰地梳理逻辑,确保条件渲染的正确性和性能。可以通过合理分组、缓存计算结果等方式来优化性能。例如,如果商品类别和库存状态的判断计算量较大,可以将这些计算结果缓存起来,避免在每次列表渲染时重复计算。

同时,对于这种复杂的嵌套结构,代码的可读性也很重要。可以考虑将一些复杂的条件判断逻辑封装成方法,在模板中调用方法,使模板代码更加简洁明了。例如:

<template>
  <ul>
    <li v - for="(product, index) in products" :key="index">
      <h3>{{ product.name }}</h3>
      <p>{{ getCategorySubtitle(product) }}</p>
      <p v - if="product.stock > 0">库存: {{ product.stock }}</p>
      <p v - else>该商品已售罄。</p>
    </li>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      products: [
        { id: 1, name: '手机', category: 'electronics', stock: 10 },
        { id: 2, name: 'T恤', category: 'clothes', stock: 0 }
      ]
    };
  },
  methods: {
    getCategorySubtitle(product) {
      if (product.category === 'electronics') {
        return '这是电子产品类商品。';
      } else if (product.category === 'clothes') {
        return '这是服装类商品。';
      } else {
        return '其他类商品。';
      }
    }
  }
};
</script>

这样不仅提高了代码的可读性,也便于维护和扩展。在实际项目中,根据具体业务场景,灵活运用条件渲染和列表渲染的结合,同时注重性能优化和代码的可维护性,是前端开发工程师需要不断修炼的技能。

在条件渲染与列表渲染结合使用时,还需要注意数据更新的一致性。例如,当通过条件渲染控制某个列表项的显示隐藏,而该列表项的数据又与其他部分相关联时,要确保数据的更新不会导致逻辑错误。假设我们有一个任务列表,每个任务可以标记为重要或不重要,并且根据重要性进行条件渲染。同时,任务还有一个进度状态,当任务完成时,我们需要更新任务列表的整体状态。

<template>
  <div>
    <ul>
      <li v - for="(task, index) in tasks" :key="index">
        <span v - if="task.isImportant">重要任务: </span>
        <span>{{ task.title }}</span>
        <input type="checkbox" :checked="task.completed" @change="updateTaskCompletion(task)" />
      </li>
    </ul>
    <p v - if="allTasksCompleted">所有任务已完成!</p>
  </div>
</template>

<script>
export default {
  data() {
    return {
      tasks: [
        { id: 1, title: '任务 1', isImportant: true, completed: false },
        { id: 2, title: '任务 2', isImportant: false, completed: false }
      ],
      allTasksCompleted: false
    };
  },
  methods: {
    updateTaskCompletion(task) {
      task.completed =!task.completed;
      const allCompleted = this.tasks.every(task => task.completed);
      this.allTasksCompleted = allCompleted;
    }
  }
};
</script>

在这个例子中,当用户点击任务的完成复选框时,不仅要更新当前任务的 completed 状态,还要检查所有任务是否都已完成,从而更新 allTasksCompleted 的状态,以正确显示“所有任务已完成!”的提示信息。这就要求我们在处理条件渲染和列表渲染结合的逻辑时,要全面考虑数据之间的关联和影响,确保整个应用的状态保持一致和正确。

此外,在使用 Vue 的响应式原理时,也要注意条件渲染和列表渲染对数据响应式的影响。如果在条件渲染或列表渲染中使用了非响应式数据,可能会导致界面更新不及时。例如,假设我们有一个列表,根据某个标志位决定是否显示列表项的详细信息。如果这个标志位不是响应式数据,即使标志位的值在逻辑中发生了变化,界面也不会相应更新。

<template>
  <ul>
    <li v - for="(item, index) in items" :key="index">
      <span>{{ item.title }}</span>
      <template v - if="showDetails">
        <p>{{ item.details }}</p>
      </template>
    </li>
    <button @click="toggleDetails">切换详情显示</button>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { title: '项目 1', details: '详细信息 1' },
        { title: '项目 2', details: '详细信息 2' }
      ],
      showDetails: false // 初始为非响应式数据
    };
  },
  methods: {
    toggleDetails() {
      this.showDetails =!this.showDetails; // 这里不会触发界面更新
    }
  }
};
</script>

为了解决这个问题,我们需要确保 showDetails 是响应式数据。可以通过将其定义在 data 函数中,利用 Vue 的响应式系统来实现数据变化时的界面更新。

<template>
  <ul>
    <li v - for="(item, index) in items" :key="index">
      <span>{{ item.title }}</span>
      <template v - if="showDetails">
        <p>{{ item.details }}</p>
      </template>
    </li>
    <button @click="toggleDetails">切换详情显示</button>
  </ul>
</template>

<script>
export default {
  data() {
    return {
      items: [
        { title: '项目 1', details: '详细信息 1' },
        { title: '项目 2', details: '详细信息 2' }
      ],
      showDetails: false // 定义为响应式数据
    };
  },
  methods: {
    toggleDetails() {
      this.showDetails =!this.showDetails; // 触发界面更新
    }
  }
};
</script>

总之,在 Vue 开发中,条件渲染与列表渲染的结合使用是非常常见且强大的功能,但需要我们深入理解其原理,关注性能、数据一致性和响应式等多个方面,才能开发出高效、稳定且易于维护的前端应用。

在大型项目中,条件渲染和列表渲染的逻辑可能会分布在多个组件中,这时候组件之间的通信和数据管理就变得尤为重要。例如,我们有一个电商应用,商品列表展示在一个组件中,而商品筛选条件在另一个组件中。根据筛选条件的变化,商品列表需要进行相应的条件渲染和列表更新。

<!-- ProductList.vue -->
<template>
  <ul>
    <li v - for="(product, index) in filteredProducts" :key="index">
      <span>{{ product.name }}</span>
      <p v - if="product.price < filterPrice">价格: {{ product.price }}</p>
      <p v - else>价格超出筛选范围。</p>
    </li>
  </ul>
</template>

<script>
export default {
  props: {
    products: {
      type: Array,
      required: true
    },
    filterPrice: {
      type: Number,
      default: 0
    }
  },
  computed: {
    filteredProducts() {
      return this.products.filter(product => product.category === this.filterCategory);
    }
  }
};
</script>
<!-- FilterComponent.vue -->
<template>
  <div>
    <input type="number" v - model="filterPrice" placeholder="筛选价格" />
    <select v - model="filterCategory">
      <option value="electronics">电子产品</option>
      <option value="clothes">服装</option>
    </select>
    <button @click="applyFilters">应用筛选</button>
  </div>
</template>

<script>
export default {
  data() {
    return {
      filterPrice: 0,
      filterCategory: 'electronics'
    };
  },
  methods: {
    applyFilters() {
      this.$emit('filter - changed', {
        filterPrice: this.filterPrice,
        filterCategory: this.filterCategory
      });
    }
  }
};
</script>
<!-- App.vue -->
<template>
  <div>
    <FilterComponent @filter - changed="handleFilterChange" />
    <ProductList :products="products" :filterPrice="filterPrice" :filterCategory="filterCategory" />
  </div>
</template>

<script>
import FilterComponent from './FilterComponent.vue';
import ProductList from './ProductList.vue';

export default {
  components: {
    FilterComponent,
    ProductList
  },
  data() {
    return {
      products: [
        { id: 1, name: '手机', category: 'electronics', price: 5000 },
        { id: 2, name: 'T恤', category: 'clothes', price: 100 }
      ],
      filterPrice: 0,
      filterCategory: 'electronics'
    };
  },
  methods: {
    handleFilterChange(filters) {
      this.filterPrice = filters.filterPrice;
      this.filterCategory = filters.filterCategory;
    }
  }
};
</script>

在这个例子中,FilterComponent 通过 $emit 触发 filter - changed 事件,将筛选条件传递给父组件 App.vueApp.vue 接收到事件后,更新自身的 filterPricefilterCategory 数据,并将这些数据传递给 ProductList.vueProductList.vue 根据接收到的数据进行条件渲染和列表过滤,展示符合条件的商品列表。通过这种组件间的通信和数据管理机制,我们可以有效地在大型项目中协调条件渲染和列表渲染的逻辑,确保应用的各个部分能够协同工作。

同时,在处理条件渲染和列表渲染的复杂逻辑时,单元测试也是非常重要的。例如,对于上述商品列表根据筛选条件进行渲染的功能,我们可以使用 Jest 和 Vue - Test - Utils 来编写单元测试。

import { mount } from '@vue/test - utils';
import ProductList from './ProductList.vue';

describe('ProductList.vue', () => {
  it('should filter products by price', () => {
    const products = [
      { id: 1, name: '手机', category: 'electronics', price: 5000 },
      { id: 2, name: 'T恤', category: 'clothes', price: 100 }
    ];
    const wrapper = mount(ProductList, {
      propsData: {
        products,
        filterPrice: 1000
      }
    });
    const visibleProducts = wrapper.findAll('li');
    expect(visibleProducts.length).toBe(1);
    expect(visibleProducts.at(0).text()).toContain('T恤');
  });
});

通过编写单元测试,我们可以验证条件渲染和列表渲染的逻辑是否正确,确保在代码发生变化时,功能依然保持稳定。这在团队协作开发和项目长期维护过程中,对于保证代码质量和减少 bug 具有重要意义。

综上所述,在 Vue 前端开发中,条件渲染与列表渲染的最佳实践涉及到语法的熟练运用、性能优化、数据管理、组件通信以及单元测试等多个方面。只有全面掌握这些知识和技巧,并根据实际项目需求灵活运用,才能开发出高质量、高性能的前端应用程序。在日常开发中,不断积累经验,关注最新的技术动态和优化方法,也是提升前端开发能力的重要途径。例如,随着 Vue 框架的不断发展,新的特性和优化策略可能会不断涌现,及时学习和应用这些新内容,可以让我们的应用始终保持高效和先进。同时,通过阅读优秀的开源项目代码,也可以学习到更多关于条件渲染和列表渲染的高级用法和优化思路,从而进一步提升自己的技术水平。在面对复杂业务场景时,要善于将大问题分解为小的逻辑模块,分别进行条件渲染和列表渲染的设计与实现,确保每个模块的功能清晰、性能良好,并且模块之间能够协同工作,最终构建出一个稳定、流畅且用户体验良好的前端应用。