Vue3 Composition API中,如何通过响应式状态与computed组合实现复杂条件渲染?

49 阅读15分钟

Composition API中的条件渲染基础

在Vue3的Composition API中,条件渲染的核心是响应式状态模板指令的配合。我们通过refreactive声明响应式变量,再用v-if/v-else等指令控制DOM的渲染逻辑。

比如一个简单的“弹窗开关”场景:

<script setup>
import { ref } from 'vue'

// 用ref声明响应式状态(初始隐藏弹窗)
const isModalOpen = ref(false)

// 切换弹窗状态的方法
const toggleModal = () => {
  isModalOpen.value = !isModalOpen.value
}
</script>

<template>
  <button @click="toggleModal">打开弹窗</button>
  <!-- v-if根据isModalOpen的值渲染/销毁弹窗 -->
  <div class="modal" v-if="isModalOpen">
    <p>这是Vue3的条件渲染示例</p>
    <button @click="toggleModal">关闭</button>
  </div>
</template>

<style scoped>
.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 2rem;
  background: white;
  border-radius: 8px;
  box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
</style>

这里的isModalOpen是响应式变量,点击按钮时修改它的值,v-if会自动响应状态变化——条件为真时渲染弹窗,为假时销毁弹窗(官网强调:v-if是“真实”的条件渲染,会完整生命周期管理组件)。

多条件组合:用Computed简化逻辑

当条件变得复杂(比如需要判断多个状态),直接在模板写冗长的逻辑会降低可读性。这时**computed计算属性**是最佳选择——它能封装复杂逻辑,且基于依赖缓存结果。

比如“用户权限控制”场景:不同角色(管理员/编辑/普通用户)显示不同操作按钮:

<script setup>
import { ref, computed } from 'vue'

// 模拟用户角色(实际从接口获取)
const userRole = ref('editor')

// 用computed封装权限条件
const canEdit = computed(() => userRole.value === 'editor' || userRole.value === 'admin')
const canDelete = computed(() => userRole.value === 'admin')
const canManage = computed(() => userRole.value === 'admin' || userRole.value === 'manager')
</script>

<template>
  <div class="actions">
    <button v-if="canEdit">编辑</button>
    <button v-if="canDelete" class="danger">删除</button>
    <button v-if="canManage" class="primary">管理</button>
  </div>
</template>

<style scoped>
.actions button { margin-right: 1rem; padding: 0.5rem 1rem; border: none; border-radius: 4px; cursor: pointer; }
.primary { background: #2196F3; color: white; }
.danger { background: #FF5722; color: white; }
</style>

computed的优势在于:逻辑与模板分离,当userRole变化时,所有依赖它的计算属性会自动更新,模板无需修改。

动态组件的条件渲染

在复杂场景中,我们常需要根据条件切换不同的组件(比如Tab栏、步骤条)。Vue3的<component :is="...">动态组件结合Composition API,可以轻松实现这一点。

比如“Tab切换”场景:

<script setup>
import { ref } from 'vue'
// 导入需要切换的组件
import Home from './Home.vue'
import Profile from './Profile.vue'
import Settings from './Settings.vue'

// 当前激活的Tab(响应式状态)
const activeTab = ref('Home')

// 根据activeTab返回对应组件
const getCurrentComponent = () => {
  switch (activeTab.value) {
    case 'Home': return Home
    case 'Profile': return Profile
    case 'Settings': return Settings
    default: return Home
  }
}
</script>

<template>
  <div class="tabs">
    <button 
      v-for="tab in ['Home', 'Profile', 'Settings']" 
      :key="tab" 
      @click="activeTab = tab"
      :class="{ active: activeTab === tab }"
    >
      {{ tab }}
    </button>
  </div>
  <!-- 动态渲染组件 -->
  <component :is="getCurrentComponent()" class="tab-content"></component>
</template>

<style scoped>
.tabs button { padding: 0.5rem 1rem; border: 1px solid #eee; border-bottom: none; background: white; cursor: pointer; }
.tabs .active { background: #f5f5f5; font-weight: bold; }
.tab-content { padding: 1rem; border: 1px solid #eee; margin-top: -1px; }
</style>

activeTab是响应式状态,点击Tab时更新它的值,getCurrentComponent根据activeTab返回对应组件,<component :is="...">负责动态渲染——这是Vue中动态组件的标准写法(官网参考:vuejs.org/guide/essen…

列表中的条件渲染:过滤与状态切换

处理列表时,我们常需要根据条件过滤项根据状态显示不同样式。比如“Todo列表”场景:

往期文章归档
免费好用的热门在线工具
<script setup>
import { ref, computed } from 'vue'

// 模拟Todo数据
const todos = ref([
  { id: 1, text: '学习Composition API', completed: false },
  { id: 2, text: '写条件渲染博客', completed: true },
  { id: 3, text: '运动半小时', completed: false }
])

// 用computed过滤未完成的Todo
const incompleteTodos = computed(() => todos.value.filter(t => !t.completed))

// 切换Todo完成状态
const toggleComplete = (id) => {
  const todo = todos.value.find(t => t.id === id)
  if (todo) todo.completed = !todo.completed
}
</script>

<template>
  <h3>未完成的任务</h3>
  <ul class="todo-list">
    <li 
      v-for="todo in incompleteTodos" 
      :key="todo.id" 
      @click="toggleComplete(todo.id)"
      :class="{ completed: todo.completed }"
    >
      {{ todo.text }}
    </li>
  </ul>
</template>

<style scoped>
.todo-list { list-style: none; padding: 0; }
.todo-list li { padding: 0.5rem 1rem; border-bottom: 1px solid #eee; cursor: pointer; }
.completed { text-decoration: line-through; color: #888; }
</style>

这里incompleteTodos是计算属性,过滤出未完成的Todo;toggleComplete方法修改Todo的completed状态,模板通过v-bind:class切换样式——列表中的条件渲染核心是“响应式数据过滤”+“状态驱动样式”

性能优化:v-show vs v-if

选择v-show还是v-if是条件渲染的常见问题,两者的核心区别在于DOM的存在性

  • v-if:条件为假时,元素会被销毁(从DOM中移除);条件为真时重新创建。
  • v-show:条件为假时,元素会被隐藏(设置display: none),但始终存在于DOM中。

比如“频繁切换的Tab”场景,用v-show更高效:

<script setup>
import { ref } from 'vue'

const activeTab = ref('tab1')
</script>

<template>
  <div class="tabs">
    <button @click="activeTab = 'tab1'">Tab 1</button>
    <button @click="activeTab = 'tab2'">Tab 2</button>
  </div>
  <div class="tab-content" v-show="activeTab === 'tab1'">Tab 1 内容</div>
  <div class="tab-content" v-show="activeTab === 'tab2'">Tab 2 内容</div>
</template>

<style scoped>
.tab-content { padding: 1rem; border: 1px solid #eee; margin-top: 1rem; }
</style>

官网明确建议(vuejs.org/guide/essen…

  • 频繁切换用v-show(避免重复销毁/创建组件);
  • 条件很少变化用v-if(减少初始DOM节点)。

课后Quiz:如何实现多条件动态渲染?

问题:假设你有一个电商页面,需要根据“用户等级”(普通/VIP/超级VIP)和“促销状态”(是否在促销期)显示不同的优惠组件。请用Composition API实现,并说明关键步骤。

答案解析

  1. 声明响应式状态:用ref存储用户等级和促销状态(实际从接口获取):
    const userLevel = ref('普通')
    const isPromotion = ref(true)
    
  2. 用Computed封装条件:计算每个优惠组件的显示条件:
    const showNormalDiscount = computed(() => userLevel.value === '普通' && isPromotion.value)
    const showVipDiscount = computed(() => userLevel.value === 'VIP' && isPromotion.value)
    const showSuperVipDiscount = computed(() => userLevel.value === '超级VIP' || !isPromotion.value)
    
  3. 模板中条件渲染:用v-if/v-else-if切换组件:
    <template>
      <NormalDiscount v-if="showNormalDiscount" />
      <VipDiscount v-else-if="showVipDiscount" />
      <SuperVipDiscount v-else />
    </template>
    

关键思路:用computed封装多条件逻辑,让模板更简洁;响应式状态变化时,计算属性自动更新,触发组件切换。

常见报错及解决方案

1. 错误:v-if与v-for同用导致变量未定义

报错信息Property "item" was accessed during render but is not defined
原因:Vue中v-if的优先级高于v-for(官网明确说明),同一元素上使用时,v-if会先执行,导致v-foritem变量未定义。
解决办法

  • v-for放在父元素,v-if放在子元素:
    <ul>
      <li v-for="item in list" :key="item.id">
        <span v-if="item.active">{{ item.text }}</span>
      </li>
    </ul>
    
  • computed过滤列表(推荐):
    const activeItems = computed(() => list.value.filter(item => item.active))
    
    模板中遍历activeItems
    <ul>
      <li v-for="item in activeItems" :key="item.id">{{ item.text }}</li>
    </ul>
    

2. 错误:ref未初始化导致条件异常

报错信息Cannot read properties of undefined (reading 'value')
原因ref未初始化时,初始值为undefined,比如const isShow = ref(),模板中v-if="isShow"会把undefined当作false,但异步赋值时可能导致渲染闪烁。
解决办法

  • 总是给ref初始化为明确值:
    const isShow = ref(false) // 布尔值
    const user = ref(null) // 异步数据用null
    

3. 错误:动态组件切换丢失状态

报错信息:切换Tab后,输入框内容丢失
原因v-if或动态组件切换时,组件会被销毁,状态重置。
解决办法:用<KeepAlive>缓存组件状态(官网参考:vuejs.org/guide/built…

<template>
  <KeepAlive>
    <component :is="currentComponent"></component>
  </KeepAlive>
</template>

<KeepAlive>会缓存不活动的组件实例,保留组件状态(比如输入框内容)。

参考链接

  1. Vue条件渲染官方文档:vuejs.org/guide/essen…
  2. Vue动态组件官方文档:vuejs.org/guide/essen…
  3. Vue Computed属性官方文档:vuejs.org/guide/essen…
  4. Vue KeepAlive官方文档:vuejs.org/guide/built…