Vue3 v-if、v-show、v-for 详解及示例

Vue3 v-if、v-show、v-for 详解及示例

参考

示例

  • 登录后标签页
  • 成绩评级及角色权限
  • 遍历:数组、对象、数字
  • 水果购物车

核心概念理解

这三个指令都是 Vue 的条件渲染和列表渲染指令,但它们的工作原理和使用场景完全不同。

v-if vs v-show

基本区别

vue 复制代码
<template>
  <div>
    <h2>v-if vs v-show 对比</h2>
    
    <!-- v-if:条件为 false 时,元素完全不存在于 DOM 中 -->
    <div v-if="isVisible" class="box v-if-box">
      我是 v-if 控制的元素
    </div>
    
    <!-- v-show:条件为 false 时,元素仍在 DOM 中,只是 display: none -->
    <div v-show="isVisible" class="box v-show-box">
      我是 v-show 控制的元素
    </div>
    
    <button @click="toggleVisibility">
      切换显示状态
    </button>
    
    <div class="debug-info">
      <p>v-if 元素在 DOM 中: {{ isVifInDOM }}</p>
      <p>v-show 元素在 DOM 中: {{ isVshowInDOM }}</p>
    </div>
  </div>
</template>

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

const isVisible = ref(true)

const toggleVisibility = () => {
  isVisible.value = !isVisible.value
}

// 检查元素是否在 DOM 中(用于演示)
const isVifInDOM = ref(false)
const isVshowInDOM = ref(false)

onMounted(() => {
  updateDOMStatus()
})

const updateDOMStatus = async () => {
  await nextTick()
  isVifInDOM.value = !!document.querySelector('.v-if-box')
  isVshowInDOM.value = !!document.querySelector('.v-show-box')
}

// 监听 isVisible 变化
import { watch } from 'vue'
watch(isVisible, () => {
  setTimeout(updateDOMStatus, 0)
})
</script>

<style>
.box {
  padding: 20px;
  margin: 10px 0;
  border-radius: 8px;
  text-align: center;
  font-weight: bold;
}

.v-if-box {
  background-color: #d1ecf1;
  border: 2px solid #bee5eb;
  color: #0c5460;
}

.v-show-box {
  background-color: #d4edda;
  border: 2px solid #c3e6cb;
  color: #155724;
}

.debug-info {
  margin-top: 20px;
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 8px;
}
</style>

详细对比表格

特性 v-if v-show
渲染方式 条件为 false 时不渲染 始终渲染,通过 CSS 控制显示
DOM 操作 频繁切换时开销大 切换开销小
初始渲染 条件为 false 时无开销 始终有开销
适用场景 很少切换的条件 频繁切换的条件

实际应用场景

vue 复制代码
<template>
  <div class="condition-demo">
    <h2>条件渲染实际应用</h2>
    
    <!-- 用户登录状态显示 -->
    <div class="user-section">
      <div v-if="isLoggedIn">
        <h3>欢迎回来,{{ userInfo.name }}!</h3>
        <button @click="logout">退出登录</button>
      </div>
      
      <div v-else>
        <h3>请登录</h3>
        <button @click="login">登录</button>
      </div>
    </div>
    
    <!-- 加载状态 -->
    <div class="loading-section">
      <!-- 加载中 - 使用 v-if,因为不经常切换 -->
      <div v-if="isLoading" class="loading">
        <div class="spinner"></div>
        <p>加载中...</p>
      </div>
      
      <!-- 内容显示 - 使用 v-show,可能频繁切换 -->
      <div v-show="!isLoading && !isError" class="content">
        <h3>内容区域</h3>
        <p>这里是主要内容...</p>
      </div>
      
      <!-- 错误提示 - 使用 v-if,因为不经常出现 -->
      <div v-if="isError" class="error">
        <h3>❌ 加载失败</h3>
        <p>{{ errorMessage }}</p>
        <button @click="retry">重试</button>
      </div>
    </div>
    
    <!-- Tab 切换 - 使用 v-show,因为频繁切换 -->
    <div class="tab-demo">
      <div class="tab-buttons">
        <button 
          v-for="tab in tabs" 
          :key="tab.id"
          :class="{ active: activeTab === tab.id }"
          @click="activeTab = tab.id"
        >
          {{ tab.name }}
        </button>
      </div>
      
      <div class="tab-content">
        <div v-show="activeTab === 'home'" class="tab-pane">
          <h3>首页内容</h3>
          <p>欢迎来到首页</p>
        </div>
        
        <div v-show="activeTab === 'profile'" class="tab-pane">
          <h3>个人资料</h3>
          <p>这里是个人资料页面</p>
        </div>
        
        <div v-show="activeTab === 'settings'" class="tab-pane">
          <h3>设置</h3>
          <p>系统设置页面</p>
        </div>
      </div>
    </div>
  </div>
</template>

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

const isLoggedIn = ref(false)
const isLoading = ref(false)
const isError = ref(false)
const errorMessage = ref('')

const userInfo = ref({
  name: 'Alice',
  email: 'alice@example.com'
})

const activeTab = ref('home')

const tabs = ref([
  { id: 'home', name: '首页' },
  { id: 'profile', name: '个人资料' },
  { id: 'settings', name: '设置' }
])

const login = () => {
  isLoading.value = true
  // 模拟异步登录
  setTimeout(() => {
    isLoggedIn.value = true
    isLoading.value = false
  }, 1000)
}

const logout = () => {
  isLoggedIn.value = false
}

const retry = () => {
  isLoading.value = true
  isError.value = false
  // 模拟重试
  setTimeout(() => {
    // 随机模拟成功或失败
    if (Math.random() > 0.5) {
      isLoading.value = false
    } else {
      isLoading.value = false
      isError.value = true
      errorMessage.value = '网络连接失败,请稍后重试'
    }
  }, 1000)
}
</script>

<style>
.condition-demo {
  max-width: 800px;
  margin: 0 auto;
  padding: 20px;
}

.user-section {
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
  margin-bottom: 20px;
  text-align: center;
}

.loading-section {
  padding: 20px;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 8px;
  margin-bottom: 20px;
  min-height: 150px;
}

.loading {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  height: 100px;
}

.spinner {
  width: 40px;
  height: 40px;
  border: 4px solid #f3f3f3;
  border-top: 4px solid #007bff;
  border-radius: 50%;
  animation: spin 1s linear infinite;
}

@keyframes spin {
  0% { transform: rotate(0deg); }
  100% { transform: rotate(360deg); }
}

.content {
  padding: 20px;
  background-color: #d4edda;
  border: 1px solid #c3e6cb;
  border-radius: 8px;
}

.error {
  padding: 20px;
  background-color: #f8d7da;
  border: 1px solid #f5c6cb;
  border-radius: 8px;
  text-align: center;
}

.tab-demo {
  margin-top: 30px;
}

.tab-buttons {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

.tab-buttons button {
  padding: 10px 20px;
  border: none;
  border-radius: 4px;
  background-color: #e9ecef;
  cursor: pointer;
  transition: all 0.2s ease;
}

.tab-buttons button:hover {
  background-color: #dee2e6;
}

.tab-buttons button.active {
  background-color: #007bff;
  color: white;
}

.tab-pane {
  padding: 20px;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 8px;
}
</style>

v-if 的配套指令

v-else 和 v-else-if

vue 复制代码
<template>
  <div class="if-else-demo">
    <h2>v-if 配套指令</h2>
    
    <div class="score-input">
      <label>输入分数 (0-100):</label>
      <input 
        type="number" 
        v-model.number="score" 
        min="0" 
        max="100"
      >
    </div>
    
    <div class="grade-display">
      <!-- 多重条件判断 -->
      <div v-if="score >= 90" class="grade excellent">
        <h3>🏆 优秀 (A)</h3>
        <p>成绩优异,继续保持!</p>
      </div>
      <div v-else-if="score >= 80" class="grade good">
        <h3>👍 良好 (B)</h3>
        <p>不错的表现,还有提升空间</p>
      </div>
      <div v-else-if="score >= 70" class="grade average">
        <h3>👌 一般 (C)</h3>
        <p>需要更加努力哦</p>
      </div>
      <div v-else-if="score >= 60" class="grade poor">
        <h3>⚠️ 及格 (D)</h3>
        <p>刚好及格,要加油了</p>
      </div>
      <div v-else-if="score >= 0" class="grade fail">
        <h3>❌ 不及格 (F)</h3>
        <p>需要认真复习</p>
      </div>
      <div v-else class="grade empty">
        <h3>📝 请输入分数</h3>
        <p>请输入有效的分数</p>
      </div>
    </div>
    
    <!-- 权限控制示例 -->
    <div class="permission-demo">
      <h3>用户权限演示</h3>
      <select v-model="userRole">
        <option value="guest">访客</option>
        <option value="user">普通用户</option>
        <option value="admin">管理员</option>
        <option value="super">超级管理员</option>
      </select>
      
      <div class="permission-content">
        <div v-if="userRole === 'super'" class="admin-content">
          <h4>👑 超级管理员权限</h4>
          <ul>
            <li>查看所有数据</li>
            <li>删除用户</li>
            <li>系统配置</li>
            <li>备份数据库</li>
          </ul>
        </div>
        <div v-else-if="userRole === 'admin'" class="admin-content">
          <h4>🔧 管理员权限</h4>
          <ul>
            <li>查看所有数据</li>
            <li>编辑用户信息</li>
            <li>审核内容</li>
          </ul>
        </div>
        <div v-else-if="userRole === 'user'" class="user-content">
          <h4>👤 普通用户权限</h4>
          <ul>
            <li>查看个人信息</li>
            <li>编辑个人资料</li>
            <li>发布内容</li>
          </ul>
        </div>
        <div v-else class="guest-content">
          <h4>👀 访客权限</h4>
          <ul>
            <li>浏览公开内容</li>
            <li>注册账户</li>
          </ul>
        </div>
      </div>
    </div>
  </div>
</template>

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

const score = ref('')
const userRole = ref('guest')
</script>

<style>
.if-else-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.score-input {
  margin-bottom: 20px;
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.score-input label {
  display: block;
  margin-bottom: 10px;
  font-weight: bold;
}

.score-input input {
  width: 100%;
  padding: 10px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
}

.grade {
  padding: 20px;
  border-radius: 8px;
  margin: 10px 0;
  text-align: center;
}

.excellent {
  background-color: #d4edda;
  border: 1px solid #c3e6cb;
  color: #155724;
}

.good {
  background-color: #cce7ff;
  border: 1px solid #b8daff;
  color: #004085;
}

.average {
  background-color: #fff3cd;
  border: 1px solid #ffeaa7;
  color: #856404;
}

.poor {
  background-color: #f8d7da;
  border: 1px solid #f5c6cb;
  color: #721c24;
}

.fail {
  background-color: #f2dede;
  border: 1px solid #ebcccc;
  color: #a94442;
}

.empty {
  background-color: #e2e3e5;
  border: 1px solid #d6d8db;
  color: #383d41;
}

.permission-demo {
  margin-top: 30px;
  padding: 20px;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 8px;
}

.permission-demo select {
  width: 100%;
  padding: 10px;
  margin: 10px 0;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
}

.permission-content ul {
  text-align: left;
  margin-top: 15px;
}

.permission-content li {
  margin: 8px 0;
  padding-left: 20px;
  position: relative;
}

.permission-content li::before {
  content: "•";
  position: absolute;
  left: 0;
  color: #007bff;
}
</style>

v-for 列表渲染

基础用法

vue 复制代码
<template>
  <div class="vfor-demo">
    <h2>v-for 列表渲染</h2>
    
    <!-- 遍历数组 -->
    <div class="array-demo">
      <h3>数组遍历</h3>
      <ul>
        <li v-for="(item, index) in fruits" :key="item.id">
          {{ index + 1 }}. {{ item.name }} - ¥{{ item.price }}
        </li>
      </ul>
    </div>
    
    <!-- 遍历对象 -->
    <div class="object-demo">
      <h3>对象遍历</h3>
      <div class="user-info">
        <div 
          v-for="(value, key, index) in userInfo" 
          :key="key"
          class="info-item"
        >
          <span class="key">{{ key }}:</span>
          <span class="value">{{ value }}</span>
        </div>
      </div>
    </div>
    
    <!-- 遍历数字 -->
    <div class="number-demo">
      <h3>数字遍历</h3>
      <div class="stars">
        <span 
          v-for="n in 5" 
          :key="n" 
          :class="{ filled: n <= rating }"
          @click="setRating(n)"
        >
          ⭐
        </span>
        <span class="rating-text">({{ rating }}/5)</span>
      </div>
    </div>
  </div>
</template>

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

const fruits = ref([
  { id: 1, name: '苹果', price: 5.5 },
  { id: 2, name: '香蕉', price: 3.2 },
  { id: 3, name: '橙子', price: 4.8 },
  { id: 4, name: '葡萄', price: 8.0 }
])

const userInfo = ref({
  name: 'Alice',
  age: 25,
  city: '北京',
  email: 'alice@example.com',
  phone: '138****1234'
})

const rating = ref(3)

const setRating = (newRating) => {
  rating.value = newRating
}
</script>

<style>
.vfor-demo {
  max-width: 600px;
  margin: 0 auto;
  padding: 20px;
}

.array-demo, .object-demo, .number-demo {
  margin-bottom: 30px;
  padding: 20px;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 8px;
}

.array-demo ul {
  list-style-type: none;
  padding: 0;
}

.array-demo li {
  padding: 10px;
  margin: 5px 0;
  background-color: #f8f9fa;
  border-radius: 4px;
}

.user-info {
  display: grid;
  gap: 10px;
}

.info-item {
  padding: 10px;
  background-color: #e3f2fd;
  border-radius: 4px;
  display: flex;
  justify-content: space-between;
}

.key {
  font-weight: bold;
  color: #1976d2;
}

.value {
  color: #333;
}

.stars {
  font-size: 24px;
  cursor: pointer;
}

.stars span {
  transition: all 0.2s ease;
}

.stars span:hover {
  transform: scale(1.2);
}

.stars .filled {
  color: #ffc107;
}

.rating-text {
  font-size: 16px;
  margin-left: 10px;
  color: #666;
}
</style>

v-for 与 v-if 的组合使用

vue 复制代码
<template>
  <div class="vfor-if-demo">
    <h2>v-for 与 v-if 组合使用</h2>
    
    <!-- 搜索功能 -->
    <div class="search-section">
      <input 
        v-model="searchTerm" 
        placeholder="搜索商品..."
        class="search-input"
      >
      <select v-model="categoryFilter" class="category-select">
        <option value="">所有分类</option>
        <option value="水果">水果</option>
        <option value="蔬菜">蔬菜</option>
        <option value="肉类">肉类</option>
      </select>
    </div>
    
    <!-- 商品列表 -->
    <div class="products-section">
      <h3>商品列表 ({{ filteredProducts.length }} 个结果)</h3>
      
      <!-- 方法1:在 v-for 内部使用 v-if(过滤显示) -->
      <div class="product-grid">
        <div 
          v-for="product in products" 
          :key="product.id"
          class="product-card"
        >
        <div v-if="shouldShowProduct(product)" class="product-card">
          <div class="product-image">
            <span> {{product.image}} </span>
          </div>
          <div class="product-info">
            <h4>{{ product.name }}</h4>
            <p class="category">{{ product.category }}</p>
            <p class="price">¥{{ product.price }}</p>
            <button 
              :class="{ 'in-cart': isInCart(product.id) }"
              @click="toggleCart(product.id)"
            >
              {{ isInCart(product.id) ? '.removeFromCart' : 'addToCart' }}
            </button>
          </div>
        </div>
        </div>
      </div>
      
      <!-- 方法2:使用计算属性预先过滤(推荐) -->
      <div class="product-grid">
        <div 
          v-for="product in filteredProducts" 
          :key="product.id"
          class="product-card recommended"
        >
          <div class="product-image">
            <!-- <img :src="product.image" :alt="product.name"> -->
            <span> {{product.image}} </span>
          </div>
          <div class="product-info">
            <h4>{{ product.name }}</h4>
            <p class="category">{{ product.category }}</p>
            <p class="price">¥{{ product.price }}</p>
            <button 
              :class="{ 'in-cart': isInCart(product.id) }"
              @click="toggleCart(product.id)"
            >
              {{ isInCart(product.id) ? 'removeFromCart' : 'addToCart' }}
            </button>
          </div>
        </div>
      </div>
    </div>
    
    <!-- 购物车 -->
    <div class="cart-section">
      <h3>购物车 ({{ cart.length }} 项)</h3>
      <div v-if="cart.length === 0" class="empty-cart">
        购物车为空
      </div>
      <div v-else class="cart-items">
        <div 
          v-for="item in cartItems" 
          :key="item.id"
          class="cart-item"
        >
          <span>{{ item.name }} x {{ item.quantity }}</span>
          <span>¥{{ item.totalPrice }}</span>
        </div>
        <div class="cart-total">
          总计: ¥{{ cartTotal }}
        </div>
      </div>
    </div>
  </div>
</template>

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

const searchTerm = ref('')
const categoryFilter = ref('')

const products = ref([
  { id: 1, name: '红苹果', category: '水果', price: 5.5, image: '🍎' },
  { id: 2, name: '香蕉', category: '水果', price: 3.2, image: '🍌' },
  { id: 3, name: '橙子', category: '水果', price: 4.8, image: '🍊' },
  { id: 4, name: '西红柿', category: '蔬菜', price: 2.5, image: '🍅' },
  { id: 5, name: '胡萝卜', category: '蔬菜', price: 1.8, image: '🥕' },
  { id: 6, name: '牛肉', category: '肉类', price: 45.0, image: '🥩' },
  { id: 7, name: '鸡肉', category: '肉类', price: 25.0, image: '🍗' }
])

const cart = ref([])

// 计算属性:过滤后的商品
const filteredProducts = computed(() => {
  return products.value.filter(product => {
    const matchesSearch = product.name.toLowerCase().includes(searchTerm.value.toLowerCase())
    const matchesCategory = !categoryFilter.value || product.category === categoryFilter.value
    return matchesSearch && matchesCategory
  })
})

// 方法:判断是否应该显示商品
const shouldShowProduct = (product) => {
  const matchesSearch = product.name.toLowerCase().includes(searchTerm.value.toLowerCase())
  const matchesCategory = !categoryFilter.value || product.category === categoryFilter.value
  return matchesSearch && matchesCategory
}

// 购物车操作
const isInCart = (productId) => {
  return cart.value.some(item => item.id === productId)
}

const toggleCart = (productId) => {
  const index = cart.value.findIndex(item => item.id === productId)
  if (index > -1) {
    cart.value.splice(index, 1)
  } else {
    const product = products.value.find(p => p.id === productId)
    if (product) {
      cart.value.push({ ...product, quantity: 1 })
    }
  }
}

// 计算购物车总价
const cartItems = computed(() => {
  return cart.value.map(item => ({
    ...item,
    totalPrice: (item.price * item.quantity).toFixed(2)
  }))
})

const cartTotal = computed(() => {
  return cart.value.reduce((total, item) => total + (item.price * item.quantity), 0).toFixed(2)
})
</script>

<style>
.vfor-if-demo {
  max-width: 1000px;
  margin: 0 auto;
  padding: 20px;
}

.search-section {
  display: flex;
  gap: 15px;
  margin-bottom: 30px;
  padding: 20px;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.search-input, .category-select {
  padding: 10px;
  border: 1px solid #ced4da;
  border-radius: 4px;
  font-size: 16px;
}

.search-input {
  flex: 1;
}

.products-section {
  margin-bottom: 30px;
}

.product-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 20px;
  margin-top: 20px;
}

.product-card {
  border: 1px solid #dee2e6;
  border-radius: 8px;
  overflow: hidden;
  transition: all 0.3s ease;
}

.product-card:hover {
  transform: translateY(-5px);
  box-shadow: 0 4px 12px rgba(0,0,0,0.1);
}

.product-card.recommended {
  border-color: #28a745;
}

.product-image {
  height: 150px;
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #f8f9fa;
  font-size: 48px;
}

.product-info {
  padding: 15px;
}

.product-info h4 {
  margin: 0 0 10px 0;
  color: #333;
}

.category {
  color: #6c757d;
  font-size: 14px;
  margin: 5px 0;
}

.price {
  font-size: 18px;
  font-weight: bold;
  color: #28a745;
  margin: 10px 0;
}

.product-info button {
  width: 100%;
  padding: 10px;
  border: none;
  border-radius: 4px;
  background-color: #007bff;
  color: white;
  cursor: pointer;
  transition: all 0.2s ease;
}

.product-info button:hover {
  background-color: #0056b3;
}

.product-info button.in-cart {
  background-color: #28a745;
}

.product-info button.in-cart:hover {
  background-color: #1e7e34;
}

.cart-section {
  padding: 20px;
  background-color: #fff;
  border: 1px solid #dee2e6;
  border-radius: 8px;
}

.empty-cart {
  text-align: center;
  color: #6c757d;
  padding: 40px;
}

.cart-items {
  margin-top: 20px;
}

.cart-item {
  display: flex;
  justify-content: space-between;
  padding: 10px;
  border-bottom: 1px solid #eee;
}

.cart-total {
  font-size: 18px;
  font-weight: bold;
  text-align: right;
  margin-top: 20px;
  color: #28a745;
}
</style>

重要注意事项

1. v-for 必须使用 key

vue 复制代码
<template>
  <div>
    <!-- ✅ 正确:使用唯一 key -->
    <div v-for="item in items" :key="item.id">
      {{ item.name }}
    </div>
    
    <!-- ❌ 错误:没有 key(不推荐) -->
    <div v-for="item in items">
      {{ item.name }}
    </div>
    
    <!-- ⚠️ 危险:使用 index 作为 key(可能导致问题) -->
    <div v-for="(item, index) in items" :key="index">
      <input v-model="item.name">
    </div>
  </div>
</template>

2. v-for 和 v-if 的优先级

vue 复制代码
<template>
  <div>
    <!-- ❌ 不推荐:v-for 内部使用 v-if -->
    <div v-for="user in users" :key="user.id" v-if="user.active">
      {{ user.name }}
    </div>
    
    <!-- ✅ 推荐:使用计算属性预先过滤 -->
    <div v-for="user in activeUsers" :key="user.id">
      {{ user.name }}
    </div>
    
    <!-- ✅ 或者:在容器元素上使用 v-if -->
    <div v-if="showActiveUsers">
      <div v-for="user in activeUsers" :key="user.id">
        {{ user.name }}
      </div>
    </div>
  </div>
</template>

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

const users = ref([
  { id: 1, name: 'Alice', active: true },
  { id: 2, name: 'Bob', active: false },
  { id: 3, name: 'Charlie', active: true }
])

const showActiveUsers = ref(true)

// 推荐:使用计算属性
const activeUsers = computed(() => {
  return users.value.filter(user => user.active)
})
</script>

3. 数组变更检测注意事项

vue 复制代码
<template>
  <div>
    <h3>数组操作示例</h3>
    <ul>
      <li v-for="(item, index) in items" :key="index">
        {{ item }}
        <button @click="removeItem(index)">删除</button>
      </li>
    </ul>
    
    <div class="controls">
      <input v-model="newItem" placeholder="输入新项目">
      <button @click="addItem">添加</button>
      <button @click="sortItems">排序</button>
      <button @click="resetItems">重置</button>
    </div>
  </div>
</template>

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

const items = ref(['苹果', '香蕉', '橙子'])
const newItem = ref('')

// ✅ Vue3 可以直接修改数组索引
const removeItem = (index) => {
  items.value.splice(index, 1)  // 推荐使用数组方法
}

// ✅ 添加项目
const addItem = () => {
  if (newItem.value.trim()) {
    items.value.push(newItem.value)
    newItem.value = ''
  }
}

// ✅ 排序
const sortItems = () => {
  items.value.sort()
}

// ✅ 重置
const resetItems = () => {
  items.value = ['苹果', '香蕉', '橙子']
}
</script>

<style>
.controls {
  margin-top: 20px;
  padding: 15px;
  background-color: #f8f9fa;
  border-radius: 8px;
}

.controls input, .controls button {
  margin: 5px;
  padding: 8px 12px;
  border: 1px solid #ced4da;
  border-radius: 4px;
}

.controls button {
  background-color: #007bff;
  color: white;
  cursor: pointer;
  border: none;
}

.controls button:hover {
  background-color: #0056b3;
}
</style>

总结

使用建议

场景 推荐指令 原因
很少切换的条件 v-if 初始渲染开销小
频繁切换的条件 v-show 切换开销小
列表渲染 v-for 必须配合 key
多重条件 v-if/v-else-if/v-else 逻辑清晰

记忆口诀

  • v-if:真渲染,假消失(DOM 中不存在)
  • v-show:显隐藏,真假都在(CSS 控制显示)
  • v-for:循环渲染,记得 key(唯一标识)
  • 组合用:先过滤,再循环(性能更好)
相关推荐
页面仔Dony几秒前
Vue2 与 Vue3 深度对比
vue.js·前端框架
用户904706683573 分钟前
TL如何进行有效的CR
前端
富婆苗子6 分钟前
关于wangeditor的自定义组件和元素
前端·javascript
顾辰逸you8 分钟前
uniapp--咸虾米壁纸(三)
前端·微信小程序
北鸟南游11 分钟前
用现有bootstrap的模板,改造成nuxt3项目
前端·bootstrap·nuxt.js
前端老鹰13 分钟前
JavaScript Intl.RelativeTimeFormat:自动生成 “3 分钟前” 的国际化工具
前端·javascript
梦想CAD控件13 分钟前
(在线CAD插件)网页CAD实现图纸表格智能提取
前端·javascript·全栈
木子雨廷31 分钟前
Flutter 开发一个plugin
前端·flutter
重生之我是一名前端程序员33 分钟前
websocket + xterm 前端实现网页版终端
前端·websocket
sorryhc35 分钟前
【AI解读源码系列】ant design mobile——Space间距
前端·javascript·react.js