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 'form': return 'dynamic-form'
      default: return 'default-view'
    }
  }
}

總結

條件渲染和列表渲染是uni-app開發中的核心技能,正確使用這些技術可以創建動態、高效的用戶界面。關鍵要點包括:

  1. 選擇合適的條件渲染指令:根據使用場景選擇v-ifv-show
  2. 正確使用key屬性:為列表項提供唯一的key值
  3. 注意性能優化:使用計算屬性、避免複雜表達式、合理使用虛擬列表
  4. 遵循最佳實踐:避免在同一元素上同時使用v-forv-if
  5. 處理響應式更新:使用Vue提供的響應式方法修改數據

通過掌握這些技術和最佳實踐,你可以構建出性能優秀、用戶體驗良好的uni-app應用。

一次開發,多端部署 - 讓跨平台開發更簡單