Skip to content

条件渲染与列表渲染

在uni-app开发中,条件渲染和列表渲染是两种常用的动态渲染方式,可以根据数据状态动态显示或隐藏内容,以及高效地渲染列表数据。本文将详细介绍这两种渲染方式的使用方法和最佳实践。

条件渲染

条件渲染允许我们根据特定条件决定是否渲染某些元素。uni-app支持Vue的所有条件渲染指令。

v-if / v-else-if / v-else

v-if 指令用于条件性地渲染一块内容。这块内容只会在指令的表达式返回 truthy 值时被渲染。

html
<view v-if="type === 'A'">A类型内容</view>
<view v-else-if="type === 'B'">B类型内容</view>
<view v-else-if="type === 'C'">C类型内容</view>
<view v-else>其他类型内容</view>
javascript
export default {
  data() {
    return {
      type: 'B'
    }
  }
}

在上面的例子中,只有B类型内容会被渲染,其他内容会被完全移除。

v-show

v-show 指令也用于条件性地显示元素,但与 v-if 不同的是,v-show 只是简单地切换元素的CSS属性 display

html
<view v-show="isVisible">这个内容会根据isVisible的值显示或隐藏</view>
javascript
export default {
  data() {
    return {
      isVisible: true
    }
  }
}

v-if 与 v-show 的区别

  1. 渲染机制不同

    • v-if 是"真正"的条件渲染,它会确保在切换过程中条件块内的事件监听器和子组件适当地被销毁和重建
    • v-show 不管初始条件如何,元素总是会被渲染,只是简单地基于CSS的display属性进行切换
  2. 性能消耗不同

    • v-if 有更高的切换开销,适合不经常改变的场景
    • v-show 有更高的初始渲染开销,适合频繁切换的场景
html
<!-- 不经常改变的条件,使用v-if -->
<view v-if="userType === 'admin'">管理员面板</view>

<!-- 频繁切换的条件,使用v-show -->
<view v-show="isModalVisible" class="modal">弹窗内容</view>

条件渲染分组

如果需要切换多个元素,可以使用 <template> 元素包裹它们,并在上面使用 v-if。最终的渲染结果不会包含 <template> 元素。

html
<template v-if="loginType === 'username'">
  <input placeholder="请输入用户名" />
  <input placeholder="请输入密码" type="password" />
</template>
<template v-else>
  <input placeholder="请输入邮箱" />
  <input placeholder="请输入密码" type="password" />
</template>

使用计算属性进行条件渲染

对于复杂的条件逻辑,建议使用计算属性:

html
<view v-if="showMessage">{{ message }}</view>
javascript
export default {
  data() {
    return {
      message: '欢迎访问',
      userRole: 'guest',
      isLoggedIn: false
    }
  },
  computed: {
    showMessage() {
      // 复杂条件逻辑
      return this.isLoggedIn && (this.userRole === 'admin' || this.userRole === 'editor')
    }
  }
}

列表渲染

列表渲染用于基于一个数组来渲染一个列表。uni-app支持Vue的 v-for 指令进行列表渲染。

基本用法

v-for 指令需要使用 item in items 形式的特殊语法,其中 items 是源数据数组,而 item 是被迭代的数组元素的别名。

html
<view v-for="item in items" :key="item.id">
  {{ item.text }}
</view>
javascript
export default {
  data() {
    return {
      items: [
        { id: 1, text: '项目1' },
        { id: 2, text: '项目2' },
        { id: 3, text: '项目3' }
      ]
    }
  }
}

带索引的v-for

v-for 还支持一个可选的第二个参数,即当前项的索引。

html
<view v-for="(item, index) in items" :key="item.id">
  {{ index + 1 }}. {{ item.text }}
</view>

遍历对象

v-for 也可以用于遍历对象的属性。

html
<view v-for="(value, key, index) in object" :key="key">
  {{ index }}. {{ key }}: {{ value }}
</view>
javascript
export default {
  data() {
    return {
      object: {
        name: '张三',
        age: 25,
        city: '北京'
      }
    }
  }
}

使用key属性

为了给Vue一个提示,以便它能跟踪每个节点的身份,从而重用和重新排序现有元素,你需要为每项提供一个唯一的 key 属性。理想的 key 值是每项都有的唯一id。

html
<view v-for="item in items" :key="item.id">
  {{ item.text }}
</view>

注意

不要使用对象或数组之类的非基本类型值作为 v-for 的 key。请用字符串或数值类型的值。

数组更新检测

Vue能够检测到数组的变更方法,并触发视图更新:

  • push()
  • pop()
  • shift()
  • unshift()
  • splice()
  • sort()
  • reverse()
javascript
// 这些方法会触发视图更新
this.items.push({ id: 4, text: '项目4' })
this.items.splice(1, 1, { id: 5, text: '新项目' })

但是,由于JavaScript的限制,Vue不能检测到以下数组的变动:

  1. 当你利用索引直接设置一个数组项时,例如:this.items[index] = newValue
  2. 当你修改数组的长度时,例如:this.items.length = newLength

为了解决这些问题,你可以使用以下方法:

javascript
// 方法1:使用Vue.set / this.$set
this.$set(this.items, index, newValue)

// 方法2:使用数组的splice方法
this.items.splice(index, 1, newValue)

// 修改数组长度
this.items.splice(newLength)

显示过滤/排序后的结果

有时,我们想要显示一个数组经过过滤或排序后的版本,而不实际改变或重置原始数据。在这种情况下,可以创建一个计算属性,来返回过滤或排序后的数组。

html
<view v-for="item in filteredItems" :key="item.id">
  {{ item.text }}
</view>
javascript
export default {
  data() {
    return {
      items: [
        { id: 1, text: '项目1', completed: false },
        { id: 2, text: '项目2', completed: true },
        { id: 3, text: '项目3', completed: false }
      ],
      filterType: 'all' // 'all', 'completed', 'active'
    }
  },
  computed: {
    filteredItems() {
      switch (this.filterType) {
        case 'completed':
          return this.items.filter(item => item.completed)
        case 'active':
          return this.items.filter(item => !item.completed)
        default:
          return this.items
      }
    }
  }
}

如果不想使用计算属性,也可以使用方法:

html
<view v-for="item in filterItems(items, filterType)" :key="item.id">
  {{ item.text }}
</view>
javascript
export default {
  data() {
    return {
      items: [
        { id: 1, text: '项目1', completed: false },
        { id: 2, text: '项目2', completed: true },
        { id: 3, text: '项目3', completed: false }
      ],
      filterType: 'all'
    }
  },
  methods: {
    filterItems(items, type) {
      switch (type) {
        case 'completed':
          return items.filter(item => item.completed)
        case 'active':
          return items.filter(item => !item.completed)
        default:
          return items
      }
    }
  }
}

在v-for中使用范围值

v-for 也可以接受整数。在这种情况下,它会把模板重复对应次数。

html
<view v-for="n in 10" :key="n">{{ n }}</view>

这会渲染出1到10的列表。

在template上使用v-for

类似于 v-if,你也可以在 <template> 标签上使用 v-for 来渲染一段包含多个元素的内容。

html
<template v-for="item in items">
  <view :key="'title-' + item.id" class="item-title">{{ item.title }}</view>
  <view :key="'content-' + item.id" class="item-content">{{ item.content }}</view>
</template>

v-for与v-if一起使用

注意

不推荐在同一元素上同时使用 v-ifv-for。当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。

如果你的目的是有条件地跳过循环的执行,那么可以将 v-if 置于外层元素(或 <template>)上:

html
<!-- 推荐做法 -->
<template v-if="shouldShowList">
  <view v-for="item in items" :key="item.id">
    {{ item.text }}
  </view>
</template>

如果你的目的是有条件地跳过单个循环项,那么可以将 v-if 放在 v-for 的内部元素上:

html
<view v-for="item in items" :key="item.id">
  <view v-if="!item.isHidden">
    {{ item.text }}
  </view>
</view>

实际应用示例

商品列表与筛选

html
<template>
  <view class="container">
    <!-- 筛选条件 -->
    <view class="filter">
      <text>价格区间:</text>
      <view class="price-range">
        <view 
          v-for="(range, index) in priceRanges" 
          :key="index"
          :class="['range-item', currentPriceRange === index ? 'active' : '']"
          @tap="setPriceRange(index)"
        >
          {{ range.text }}
        </view>
      </view>
      
      <text>分类:</text>
      <view class="categories">
        <view 
          v-for="(category, index) in categories" 
          :key="index"
          :class="['category-item', currentCategory === index ? 'active' : '']"
          @tap="setCategory(index)"
        >
          {{ category.name }}
        </view>
      </view>
    </view>
    
    <!-- 商品列表 -->
    <view class="product-list">
      <view v-if="filteredProducts.length === 0" class="empty-tip">
        没有找到符合条件的商品
      </view>
      <view 
        v-for="product in filteredProducts" 
        :key="product.id"
        class="product-item"
      >
        <image :src="product.image" mode="aspectFill" class="product-image"></image>
        <view class="product-info">
          <text class="product-name">{{ product.name }}</text>
          <text class="product-price">¥{{ product.price.toFixed(2) }}</text>
          <text v-if="product.stock <= 0" class="out-of-stock">已售罄</text>
          <text v-else-if="product.stock < 10" class="low-stock">库存紧张</text>
        </view>
      </view>
    </view>
  </view>
</template>

<script>
export default {
  data() {
    return {
      products: [
        { id: 1, name: '商品1', price: 99.8, category: 0, stock: 20, image: '/static/product1.jpg' },
        { id: 2, name: '商品2', price: 199.5, category: 1, stock: 5, image: '/static/product2.jpg' },
        { id: 3, name: '商品3', price: 299.9, category: 0, stock: 0, image: '/static/product3.jpg' },
        { id: 4, name: '商品4', price: 49.9, category: 2, stock: 100, image: '/static/product4.jpg' },
        { id: 5, name: '商品5', price: 149.5, category: 1, stock: 30, image: '/static/product5.jpg' }
      ],
      priceRanges: [
        { text: '全部', min: 0, max: Infinity },
        { text: '0-100', min: 0, max: 100 },
        { text: '100-200', min: 100, max: 200 },
        { text: '200以上', min: 200, max: Infinity }
      ],
      categories: [
        { name: '全部', id: -1 },
        { name: '分类1', id: 0 },
        { name: '分类2', id: 1 },
        { name: '分类3', id: 2 }
      ],
      currentPriceRange: 0,
      currentCategory: 0
    }
  },
  computed: {
    filteredProducts() {
      const priceRange = this.priceRanges[this.currentPriceRange]
      const categoryId = this.categories[this.currentCategory].id
      
      return this.products.filter(product => {
        // 价格筛选
        const priceMatch = product.price >= priceRange.min && product.price < priceRange.max
        
        // 分类筛选
        const categoryMatch = categoryId === -1 || product.category === categoryId
        
        return priceMatch && categoryMatch
      })
    }
  },
  methods: {
    setPriceRange(index) {
      this.currentPriceRange = index
    },
    setCategory(index) {
      this.currentCategory = index
    }
  }
}
</script>

<style>
.container {
  padding: 20rpx;
}

.filter {
  margin-bottom: 30rpx;
}

.price-range, .categories {
  display: flex;
  flex-wrap: wrap;
  margin: 10rpx 0 20rpx;
}

.range-item, .category-item {
  padding: 10rpx 20rpx;
  margin-right: 20rpx;
  margin-bottom: 10rpx;
  background-color: #f5f5f5;
  border-radius: 6rpx;
}

.active {
  background-color: #007aff;
  color: #ffffff;
}

.product-list {
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
}

.product-item {
  width: 48%;
  margin-bottom: 20rpx;
  background-color: #ffffff;
  border-radius: 8rpx;
  overflow: hidden;
  box-shadow: 0 2rpx 10rpx rgba(0, 0, 0, 0.1);
}

.product-image {
  width: 100%;
  height: 200rpx;
}

.product-info {
  padding: 16rpx;
}

.product-name {
  font-size: 28rpx;
  margin-bottom: 10rpx;
  display: block;
}

.product-price {
  font-size: 32rpx;
  color: #ff6700;
  font-weight: bold;
  display: block;
}

.out-of-stock {
  font-size: 24rpx;
  color: #ff0000;
  margin-top: 10rpx;
  display: block;
}

.low-stock {
  font-size: 24rpx;
  color: #ff6700;
  margin-top: 10rpx;
  display: block;
}

.empty-tip {
  width: 100%;
  text-align: center;
  padding: 40rpx 0;
  color: #999;
  font-size: 28rpx;
}
</style>

动态表单生成

html
<template>
  <view class="container">
    <form @submit="submitForm">
      <!-- 动态生成表单项 -->
      <view v-for="(field, index) in formFields" :key="field.name">
        <view class="form-item">
          <text class="label">{{ field.label }}{{ field.required ? ' *' : '' }}</text>
          
          <!-- 文本输入框 -->
          <input 
            v-if="field.type === 'text'" 
            v-model="formData[field.name]"
            :placeholder="field.placeholder"
            class="input"
          />
          
          <!-- 数字输入框 -->
          <input 
            v-else-if="field.type === 'number'" 
            v-model="formData[field.name]"
            type="number"
            :placeholder="field.placeholder"
            class="input"
          />
          
          <!-- 单选框组 -->
          <view v-else-if="field.type === 'radio'" class="radio-group">
            <label 
              v-for="option in field.options" 
              :key="option.value"
              class="radio-label"
            >
              <radio 
                :value="option.value" 
                :checked="formData[field.name] === option.value"
                @tap="formData[field.name] = option.value"
                color="#007aff"
              />
              <text>{{ option.label }}</text>
            </label>
          </view>
          
          <!-- 复选框组 -->
          <view v-else-if="field.type === 'checkbox'" class="checkbox-group">
            <label 
              v-for="option in field.options" 
              :key="option.value"
              class="checkbox-label"
            >
              <checkbox 
                :value="option.value" 
                :checked="formData[field.name] && formData[field.name].includes(option.value)"
                @tap="toggleCheckbox(field.name, option.value)"
                color="#007aff"
              />
              <text>{{ option.label }}</text>
            </label>
          </view>
          
          <!-- 下拉选择器 -->
          <picker 
            v-else-if="field.type === 'select'"
            :range="field.options"
            range-key="label"
            :value="getSelectIndex(field.name, field.options)"
            @change="e => handleSelect(e, field.name, field.options)"
            class="picker"
          >
            <view class="picker-value">
              {{ getSelectLabel(field.name, field.options) || field.placeholder }}
            </view>
          </picker>
          
          <!-- 日期选择器 -->
          <picker 
            v-else-if="field.type === 'date'"
            mode="date"
            :value="formData[field.name] || ''"
            @change="e => formData[field.name] = e.detail.value"
            class="picker"
          >
            <view class="picker-value">
              {{ formData[field.name] || field.placeholder }}
            </view>
          </picker>
          
          <!-- 开关 -->
          <switch 
            v-else-if="field.type === 'switch'"
            :checked="formData[field.name]"
            @change="e => formData[field.name] = e.detail.value"
            color="#007aff"
            class="switch"
          />
          
          <!-- 文本域 -->
          <textarea 
            v-else-if="field.type === 'textarea'"
            v-model="formData[field.name]"
            :placeholder="field.placeholder"
            class="textarea"
          ></textarea>
        </view>
        
        <!-- 错误提示 -->
        <view v-if="errors[field.name]" class="error-tip">
          {{ errors[field.name] }}
        </view>
        
        <!-- 条件字段 -->
        <template v-if="field.dependentFields && formData[field.name]">
          <view 
            v-for="dependentField in field.dependentFields" 
            :key="dependentField.name"
            class="form-item dependent-field"
          >
            <text class="label">{{ dependentField.label }}{{ dependentField.required ? ' *' : '' }}</text>
            <input 
              v-model="formData[dependentField.name]"
              :placeholder="dependentField.placeholder"
              class="input"
            />
          </view>
        </template>
      </view>
      
      <button type="primary" form-type="submit" class="submit-btn">提交</button>
    </form>
  </view>
</template>

<script>
export default {
  data() {
    return {
      formFields: [
        {
          name: 'name',
          label: '姓名',
          type: 'text',
          placeholder: '请输入姓名',
          required: true
        },
        {
          name: 'age',
          label: '年龄',
          type: 'number',
          placeholder: '请输入年龄',
          required: true
        },
        {
          name: 'gender',
          label: '性别',
          type: 'radio',
          required: true,
          options: [
            { label: '男', value: 'male' },
            { label: '女', value: 'female' },
            { label: '其他', value: 'other' }
          ]
        },
        {
          name: 'interests',
          label: '兴趣爱好',
          type: 'checkbox',
          required: false,
          options: [
            { label: '阅读', value: 'reading' },
            { label: '音乐', value: 'music' },
            { label: '运动', value: 'sports' },
            { label: '旅行', value: 'travel' },
            { label: '电影', value: 'movies' }
          ]
        },
        {
          name: 'education',
          label: '学历',
          type: 'select',
          placeholder: '请选择学历',
          required: true,
          options: [
            { label: '高中', value: 'highschool' },
            { label: '大专', value: 'college' },
            { label: '本科', value: 'bachelor' },
            { label: '硕士', value: 'master' },
            { label: '博士', value: 'phd' }
          ]
        },
        {
          name: 'birthday',
          label: '出生日期',
          type: 'date',
          placeholder: '请选择出生日期',
          required: true
        },
        {
          name: 'hasWork',
          label: '是否有工作经验',
          type: 'switch',
          required: false,
          dependentFields: [
            {
              name: 'workYears',
              label: '工作年限',
              type: 'number',
              placeholder: '请输入工作年限',
              required: true
            },
            {
              name: 'company',
              label: '公司名称',
              type: 'text',
              placeholder: '请输入公司名称',
              required: true
            }
          ]
        },
        {
          name: 'introduction',
          label: '自我介绍',
          type: 'textarea',
          placeholder: '请输入自我介绍',
          required: false
        }
      ],
      formData: {
        interests: []
      },
      errors: {}
    }
  },
  methods: {
    toggleCheckbox(fieldName, value) {
      if (!this.formData[fieldName]) {
        this.$set(this.formData, fieldName, [])
      }
      
      const index = this.formData[fieldName].indexOf(value)
      if (index === -1) {
        this.formData[fieldName].push(value)
      } else {
        this.formData[fieldName].splice(index, 1)
      }
    },
    getSelectIndex(fieldName, options) {
      const value = this.formData[fieldName]
      if (!value) return -1
      
      return options.findIndex(option => option.value === value)
    },
    getSelectLabel(fieldName, options) {
      const value = this.formData[fieldName]
      if (!value) return ''
      
      const option = options.find(option => option.value === value)
      return option ? option.label : ''
    },
    handleSelect(e, fieldName, options) {
      const index = e.detail.value
      this.formData[fieldName] = options[index].value
    },
    validateForm() {
      this.errors = {}
      let isValid = true
      
      this.formFields.forEach(field => {
        // 检查必填字段
        if (field.required) {
          if (!this.formData[field.name] || 
              (Array.isArray(this.formData[field.name]) && this.formData[field.name].length === 0)) {
            this.errors[field.name] = `${field.label}不能为空`
            isValid = false
          }
        }
        
        // 检查依赖字段
        if (field.dependentFields && this.formData[field.name]) {
          field.dependentFields.forEach(depField => {
            if (depField.required && !this.formData[depField.name]) {
              this.errors[depField.name] = `${depField.label}不能为空`
              isValid = false
            }
          })
        }
      })
      
      return isValid
    },
    submitForm() {
      if (this.validateForm()) {
        uni.showModal({
          title: '表单提交成功',
          content: JSON.stringify(this.formData, null, 2),
          showCancel: false
        })
      } else {
        uni.showToast({
          title: '请完善表单信息',
          icon: 'none'
        })
      }
    }
  }
}
</script>

<style>
.container {
  padding: 30rpx;
}

.form-item {
  margin-bottom: 30rpx;
}

.label {
  display: block;
  margin-bottom: 10rpx;
  font-size: 28rpx;
}

.input, .textarea, .picker {
  width: 100%;
  height: 80rpx;
  border: 1rpx solid #ddd;
  border-radius: 8rpx;
  padding: 0 20rpx;
  box-sizing: border-box;
  font-size: 28rpx;
}

.textarea {
  height: 200rpx;
  padding: 20rpx;
}

.picker-value {
  height: 80rpx;
  line-height: 80rpx;
  color: #333;
}

.radio-group, .checkbox-group {
  display: flex;
  flex-wrap: wrap;
}

.radio-label, .checkbox-label {
  margin-right: 30rpx;
  margin-bottom: 20rpx;
  display: flex;
  align-items: center;
  font-size: 28rpx;
}

.dependent-field {
  margin-left: 40rpx;
  border-left: 4rpx solid #007aff;
  padding-left: 20rpx;
}

.error-tip {
  color: #ff0000;
  font-size: 24rpx;
  margin-top: -20rpx;
  margin-bottom: 20rpx;
}

.submit-btn {
  margin-top: 40rpx;
}
</style>

性能优化

使用key优化列表渲染

为列表项提供唯一的key,可以帮助Vue高效地更新虚拟DOM,提高渲染性能:

html
<!-- 推荐:使用唯一ID作为key -->
<view v-for="item in items" :key="item.id">{{ item.text }}</view>

<!-- 不推荐:使用索引作为key(除非列表是静态的) -->
<view v-for="(item, index) in items" :key="index">{{ item.text }}</view>

使用计算属性缓存结果

对于需要大量计算的列表过滤或排序操作,使用计算属性可以避免在每次重新渲染时都进行计算:

javascript
export default {
  data() {
    return {
      items: [/* 大量数据 */],
      searchQuery: ''
    }
  },
  computed: {
    // 计算属性会缓存结果,只有当依赖项变化时才会重新计算
    filteredItems() {
      return this.items.filter(item => 
        item.name.toLowerCase().includes(this.searchQuery.toLowerCase())
      )
    }
  }
}

使用v-show代替v-if进行频繁切换

对于频繁切换显示状态的元素,使用v-showv-if更高效:

html
<!-- 频繁切换的内容使用v-show -->
<view v-show="isVisible" class="modal">频繁切换的内容</view>

<!-- 很少改变的条件使用v-if -->
<view v-if="userHasPermission" class="admin-panel">管理员面板</view>

避免在v-for中使用复杂表达式

在模板中应避免使用复杂的表达式,尤其是在v-for循环中:

html
<!-- 不推荐 -->
<view v-for="item in items" :key="item.id">
  {{ item.price * item.quantity * getTaxRate(item.category) }}
</view>

<!-- 推荐:使用计算属性或方法 -->
<view v-for="item in items" :key="item.id">
  {{ getItemTotal(item) }}
</view>
javascript
methods: {
  getItemTotal(item) {
    return item.price * item.quantity * this.getTaxRate(item.category)
  },
  getTaxRate(category) {
    // 计算税率
  }
}

使用函数式组件

对于纯展示且不需要生命周期钩子的组件,可以使用函数式组件提高性能:

javascript
// 函数式组件
export default {
  functional: true,
  props: {
    items: Array,
    required: true
  },
  render(h, context) {
    return context.props.items.map(item => 
      h('view', { key: item.id }, item.text)
    )
  }
}

使用虚拟列表渲染大数据量列表

当需要渲染大量数据时,可以使用虚拟列表技术,只渲染可视区域内的元素:

html
<recycle-list 
  :list="longList" 
  :height="800" 
  :item-height="100"
>
  <template v-slot:item="{ item }">
    <view class="list-item">{{ item.text }}</view>
  </template>
</recycle-list>

分页加载数据

对于大量数据,使用分页加载而不是一次性加载全部数据:

html
<view class="list">
  <view 
    v-for="item in paginatedItems" 
    :key="item.id"
    class="list-item"
  >
    {{ item.text }}
  </view>
  
  <view v-if="hasMoreItems" class="load-more" @tap="loadMoreItems">
    加载更多
  </view>
</view>
javascript
export default {
  data() {
    return {
      items: [], // 所有数据
      page: 1,
      pageSize: 20,
      loading: false,
      hasMore: true
    }
  },
  computed: {
    paginatedItems() {
      return this.items.slice(0, this.page * this.pageSize)
    },
    hasMoreItems() {
      return this.hasMore && !this.loading
    }
  },
  methods: {
    loadMoreItems() {
      if (this.loading) return
      
      this.loading = true
      // 模拟API请求
      setTimeout(() => {
        const newItems = this.fetchItems(this.page + 1, this.pageSize)
        if (newItems.length < this.pageSize) {
          this.hasMore = false
        }
        this.items = [...this.items, ...newItems]
        this.page++
        this.loading = false
      }, 500)
    },
    fetchItems(page, pageSize) {
      // 实际应用中,这里会是一个API请求
      return Array.from({ length: pageSize }, (_, i) => ({
        id: (page - 1) * pageSize + i + 1,
        text: `Item ${(page - 1) * pageSize + i + 1}`
      }))
    }
  },
  onLoad() {
    // 初始加载
    this.items = this.fetchItems(1, this.pageSize)
  }
}

常见问题与解决方案

列表更新但视图不刷新

问题:修改了数组或对象,但视图没有更新。

解决方案

  1. 使用Vue的响应式方法修改数组:
javascript
// 不会触发视图更新
this.items[index] = newValue

// 会触发视图更新
this.$set(this.items, index, newValue)
// 或
this.items.splice(index, 1, newValue)
  1. 对于对象属性的添加或删除:
javascript
// 不会触发视图更新
this.user.newProp = value

// 会触发视图更新
this.$set(this.user, 'newProp', value)
// 或
this.user = { ...this.user, newProp: value }

v-for与v-if的性能问题

问题:在同一元素上同时使用v-for和v-if导致性能问题。

解决方案

  1. 使用计算属性先过滤数据:
javascript
computed: {
  filteredItems() {
    return this.items.filter(item => !item.isHidden)
  }
}
html
<view v-for="item in filteredItems" :key="item.id">
  {{ item.text }}
</view>
  1. 将v-if移到包装元素上:
html
<template v-for="item in items" :key="item.id">
  <view v-if="!item.isHidden">
    {{ item.text }}
  </view>
</template>

处理大量数据的渲染卡顿

问题:渲染大量数据时页面卡顿。

解决方案

  1. 使用分页或虚拟列表
  2. 使用setTimeout分批渲染:
javascript
methods: {
  renderLargeList() {
    const batchSize = 100
    const totalItems = this.allItems.length
    let currentIndex = 0
    
    const renderBatch = () => {
      const end = Math.min(currentIndex + batchSize, totalItems)
      const batch = this.allItems.slice(currentIndex, end)
      
      // 将这一批数据添加到渲染列表
      this.renderedItems = [...this.renderedItems, ...batch]
      
      currentIndex = end
      
      if (currentIndex < totalItems) {
        setTimeout(renderBatch, 50)
      }
    }
    
    renderBatch()
  }
}

条件渲染中的表单重置问题

问题:使用v-if切换表单时,表单状态会被重置。

解决方案

  1. 使用v-show代替v-if保持表单状态:
html
<form v-show="activeForm === 'login'">
  <!-- 登录表单 -->
</form>
<form v-show="activeForm === 'register'">
  <!-- 注册表单 -->
</form>
  1. 使用key属性强制保持组件状态:
html
<component :is="activeForm" :key="activeForm"></component>

动态组件的条件渲染

问题:需要根据条件渲染不同的组件。

解决方案: 使用Vue的动态组件功能:

html
<component :is="currentComponent" :props="componentProps"></component>
javascript
computed: {
  currentComponent() {
    switch(this.componentType) {
      case 'chart': return 'line-chart'
      case 'table': return 'data-table'
      case 'list': return 'item-list'
      default: return 'empty-view'
    }
  },
  componentProps() {
    // 根据组件类型返回不同的props
    return {
      data: this.data,
      config: this.config
    }
  }
}

总结

条件渲染和列表渲染是uni-app开发中非常重要的两个概念,它们允许我们根据数据状态动态地控制界面内容。合理使用这些功能,可以构建出灵活、高效的用户界面。

  • 条件渲染:使用v-if/v-else-if/v-elsev-show根据条件显示或隐藏内容
  • 列表渲染:使用v-for高效地渲染列表数据
  • 性能优化:合理使用key、计算属性、虚拟列表等技术提高渲染性能
  • 最佳实践:避免同时使用v-forv-if,使用计算属性过滤数据,合理处理大数据量渲染

在实际开发中,应根据具体场景选择合适的渲染方式,并注意性能优化,以提供流畅的用户体验。

一次开发,多端部署 - 让跨平台开发更简单