vue的注意规范之v-if 与 v-for 一起使用

探讨Vue.js中v-if与v-for指令的优先级及其最佳实践,包括如何避免同时使用,利用计算属性优化渲染效率。

 

当 v-if 与 v-for 一起使用时,v-for 具有比 v-if 更高的优先级,这意味着 v-if 将分别重复运行于每个 v-for 循环中

所以,不推荐v-if和v-for同时使用

使用推荐方式:

<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>
<ul v-if="shouldShowUsers">
  <li
    v-for="user in users"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

或者:放在计算属性遍历

computed: {
  activeUsers: function () {
    return this.users.filter(function (user) {
      return user.isActive
    })
  }
}
<ul>
  <li
    v-for="user in activeUsers"
    :key="user.id"
  >
    {{ user.name }}
  </li>
</ul>

当它们处于同一节点,v-for 的优先级比 v-if 更高,这意味着 v-if 将分别重复运行于每个 v-for 循环中。当你想为仅有的一些项渲染节点时,这种优先级的机制会十分有用,如下:

<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo }}
</li>

上面的代码只传递了未完成的 todos。

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


<ul v-if="todos.length">
<li v-for="todo in todos">
{{ todo }}
</li>
</ul>
<p v-else>No todos left!</p>

 

我用cartList动态渲染页面如下: <div class="cart-item" v-for="item in cartList" :key="item.goods_id+'-'+item.goods.goods_image"> 控制台仍然报错v-for的值不唯一,本质是页面中此时还没获取到cartList,控制台报错顺序是:index.js:54 [Vue warn]: Duplicate keys detected: '10041-http://smart-shop.itheima.net/uploads/10001/20230321/5c5c095502c09a9adae70d1d691b0166.jpg'. This may cause an update error. found in ---> <CartPage> at src/views/Layout/cart.vue <LayoutIndex> at src/views/Layout/index.vue <App> at src/App.vue <Root> warn @ vue.runtime.esm.js:4469 checkDuplicateKeys @ vue.runtime.esm.js:6499 createChildren @ vue.runtime.esm.js:6317 createElm @ vue.runtime.esm.js:6228 createChildren @ vue.runtime.esm.js:6320 createElm @ vue.runtime.esm.js:6228 createChildren @ vue.runtime.esm.js:6320 createElm @ vue.runtime.esm.js:6228 patch @ vue.runtime.esm.js:6691 Vue._update @ vue.runtime.esm.js:3657 updateComponent @ vue.runtime.esm.js:3757 Watcher.get @ vue.runtime.esm.js:3351 Watcher @ vue.runtime.esm.js:3341 mountComponent @ vue.runtime.esm.js:3778 Vue.$mount @ vue.runtime.esm.js:8308 init @ vue.runtime.esm.js:4264 merged @ vue.runtime.esm.js:4426 createComponent @ vue.runtime.esm.js:6249 createElm @ vue.runtime.esm.js:6211 createChildren @ vue.runtime.esm.js:6320 createElm @ vue.runtime.esm.js:6228 patch @ vue.runtime.esm.js:6691 Vue._update @ vue.runtime.esm.js:3657 updateComponent @ vue.runtime.esm.js:3757 Watcher.get @ vue.runtime.esm.js:3351 Watcher @ vue.runtime.esm.js:3341 mountComponent @ vue.runtime.esm.js:3778 Vue.$mount @ vue.runtime.esm.js:8308 init @ vue.runtime.esm.js:4264 merged @ vue.runtime.esm.js:4426 createComponent @ vue.runtime.esm.js:6249 createElm @ vue.runtime.esm.js:6211 updateChildren @ vue.runtime.esm.js:6470 patchVnode @ vue.runtime.esm.js:6550 patch @ vue.runtime.esm.js:6696 Vue._update @ vue.runtime.esm.js:3660 updateComponent @ vue.runtime.esm.js:3757 Watcher.get @ vue.runtime.esm.js:3351 Watcher.run @ vue.runtime.esm.js:3422 flushSchedulerQueue @ vue.runtime.esm.js:4007 eval @ vue.runtime.esm.js:3064 flushCallbacks @ vue.runtime.esm.js:2992 Promise.then(异步) timerFunc @ vue.runtime.esm.js:3017 nextTick @ vue.runtime.esm.js:3074 queueWatcher @ vue.runtime.esm.js:4089 Watcher.update @ vue.runtime.esm.js:3413 Dep.notify @ vue.runtime.esm.js:814 reactiveSetter @ vue.runtime.esm.js:1027 eval @ vue-router.esm.js:2520 eval @ vue-router.esm.js:2519 updateRoute @ vue-router.esm.js:2048 eval @ vue-router.esm.js:1915 eval @ vue-router.esm.js:2037 step @ vue-router.esm.js:1763 step @ vue-router.esm.js:1770 step @ vue-router.esm.js:1770 runQueue @ vue-router.esm.js:1774 eval @ vue-router.esm.js:2032 step @ vue-router.esm.js:1763 eval @ vue-router.esm.js:1767 eval @ vue-router.esm.js:2020 eval @ vue-router.esm.js:1833 iterator @ vue-router.esm.js:2002 step @ vue-router.esm.js:1766 step @ vue-router.esm.js:1770 step @ vue-router.esm.js:1770 eval @ vue-router.esm.js:1767 eval @ vue-router.esm.js:2020 eval @ index.js:54 iterator @ vue-router.esm.js:2002 step @ vue-router.esm.js:1766 step @ vue-router.esm.js:1770 runQueue @ vue-router.esm.js:1774 confirmTransition @ vue-router.esm.js:2027 transitionTo @ vue-router.esm.js:1914 push @ vue-router.esm.js:2197 eval @ vue-router.esm.js:2545 push @ vue-router.esm.js:2544 click @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[3]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/ProductDetail/index.vue?vue&type=template&id=70b74859&scoped=true:146 invokeWithErrorHandling @ vue.runtime.esm.js:2945 invoker @ vue.runtime.esm.js:1835 original_1._wrapper @ vue.runtime.esm.js:7063 显示另外 81 个框架 收起 index.js:54 [vuex] unknown action type: cart/setCartList dispatch @ vuex.esm.js:473 boundDispatch @ vuex.esm.js:386 created @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Layout/cart.vue?vue&type=script&lang=js:45 await in created(异步) invokeWithErrorHandling @ vue.runtime.esm.js:2945 callHook$1 @ vue.runtime.esm.js:3917 Vue._init @ vue.runtime.esm.js:5434 VueComponent @ vue.runtime.esm.js:5561 createComponentInstanceForVnode @ vue.runtime.esm.js:4409 init @ vue.runtime.esm.js:4263 merged @ vue.runtime.esm.js:4426 createComponent @ vue.runtime.esm.js:6249 createElm @ vue.runtime.esm.js:6211 createChildren @ vue.runtime.esm.js:6320 createElm @ vue.runtime.esm.js:6228 patch @ vue.runtime.esm.js:6691 Vue._update @ vue.runtime.esm.js:3657 updateComponent @ vue.runtime.esm.js:3757 Watcher.get @ vue.runtime.esm.js:3351 Watcher @ vue.runtime.esm.js:3341 mountComponent @ vue.runtime.esm.js:3778 Vue.$mount @ vue.runtime.esm.js:8308 init @ vue.runtime.esm.js:4264 merged @ vue.runtime.esm.js:4426 createComponent @ vue.runtime.esm.js:6249 createElm @ vue.runtime.esm.js:6211 updateChildren @ vue.runtime.esm.js:6470 patchVnode @ vue.runtime.esm.js:6550 patch @ vue.runtime.esm.js:6696 Vue._update @ vue.runtime.esm.js:3660 updateComponent @ vue.runtime.esm.js:3757 Watcher.get @ vue.runtime.esm.js:3351 Watcher.run @ vue.runtime.esm.js:3422 flushSchedulerQueue @ vue.runtime.esm.js:4007 eval @ vue.runtime.esm.js:3064 flushCallbacks @ vue.runtime.esm.js:2992 Promise.then(异步) timerFunc @ vue.runtime.esm.js:3017 nextTick @ vue.runtime.esm.js:3074 queueWatcher @ vue.runtime.esm.js:4089 Watcher.update @ vue.runtime.esm.js:3413 Dep.notify @ vue.runtime.esm.js:814 reactiveSetter @ vue.runtime.esm.js:1027 eval @ vue-router.esm.js:2520 eval @ vue-router.esm.js:2519 updateRoute @ vue-router.esm.js:2048 eval @ vue-router.esm.js:1915 eval @ vue-router.esm.js:2037 step @ vue-router.esm.js:1763 step @ vue-router.esm.js:1770 step @ vue-router.esm.js:1770 runQueue @ vue-router.esm.js:1774 eval @ vue-router.esm.js:2032 step @ vue-router.esm.js:1763 eval @ vue-router.esm.js:1767 eval @ vue-router.esm.js:2020 eval @ vue-router.esm.js:1833 iterator @ vue-router.esm.js:2002 step @ vue-router.esm.js:1766 step @ vue-router.esm.js:1770 step @ vue-router.esm.js:1770 eval @ vue-router.esm.js:1767 eval @ vue-router.esm.js:2020 eval @ index.js:54 iterator @ vue-router.esm.js:2002 step @ vue-router.esm.js:1766 step @ vue-router.esm.js:1770 runQueue @ vue-router.esm.js:1774 confirmTransition @ vue-router.esm.js:2027 transitionTo @ vue-router.esm.js:1914 push @ vue-router.esm.js:2197 eval @ vue-router.esm.js:2545 push @ vue-router.esm.js:2544 click @ index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/loaders/templateLoader.js??ruleSet[1].rules[3]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/ProductDetail/index.vue?vue&type=template&id=70b74859&scoped=true:146 invokeWithErrorHandling @ vue.runtime.esm.js:2945 invoker @ vue.runtime.esm.js:1835 original_1._wrapper @ vue.runtime.esm.js:7063 显示另外 74 个框架 收起 index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Layout/cart.vue?vue&type=script&lang=js:47 Store cartList: (5) [{…}, {…}, {…}, {…}, {…}, __ob__: Observer]0: create_time: "2025-07-07 19:46:33"goods: Objectgoods_id: 10039goods_num: 9goods_sku_id: "0"id: (…)isChecked: trueis_delete: (…)store_id: (…)update_time: (…)user_id: (…)__ob__: Observer {value: {…}, shallow: false, mock: false, dep: Dep, vmCount: 0}get create_time: ƒ reactiveGetter()set create_time: ƒ reactiveSetter(newVal)get goods: ƒ reactiveGetter()set goods: ƒ reactiveSetter(newVal)get goods_id: ƒ reactiveGetter()set goods_id: ƒ reactiveSetter(newVal)get goods_num: ƒ reactiveGetter()set goods_num: ƒ reactiveSetter(newVal)get goods_sku_id: ƒ reactiveGetter()set goods_sku_id: ƒ reactiveSetter(newVal)get id: ƒ reactiveGetter()set id: ƒ reactiveSetter(newVal)get isChecked: ƒ reactiveGetter()set isChecked: ƒ reactiveSetter(newVal)get is_delete: ƒ reactiveGetter()set is_delete: ƒ reactiveSetter(newVal)get store_id: ƒ reactiveGetter()set store_id: ƒ reactiveSetter(newVal)get update_time: ƒ reactiveGetter()set update_time: ƒ reactiveSetter(newVal)get user_id: ƒ reactiveGetter()set user_id: ƒ reactiveSetter(newVal)[[Prototype]]: Object1: {…}2: {…}3: {…}4: {…}length: 5__ob__: Observer {value: Array(5), shallow: false, mock: false, dep: Dep, vmCount: 0}[[Prototype]]: Array index.js??clonedRuleSet-40.use[0]!./node_modules/@vue/vue-loader-v15/lib/index.js??vue-loader-options!./src/views/Layout/cart.vue?vue&type=script&lang=js:48 Mapped cartList: (5) [{…}, {…}, {…}, {…}, {…}, __ob__: Observer],先输出报错,再输入 const { data } = await getCartList() // 2. 更新本地状态(如需要) this.mycartList = data.list await this.$store.dispatch('cart/setCartList', data.list) // 新增一个直接提交数据的 Action console.log('Store cartList:', this.$store.state.cart.cartList) // 应 data.list 一致 console.log('Mapped cartList:', this.cartList) // 应同步更新,从的console.log语句,是什么原因
07-08
### **问题分析** 根据你的日志和代码,现象如下: 1. **报错顺序**: - 先报 `Duplicate keys detected`(`v-for` 键值重复)。 - 然后输出 `Store cartList:` 和 `Mapped cartList:`(此时数据已存在)。 2. **根本原因**: - **初始渲染时 `cartList` 为空**:`v-for` 遍历空数组,不会报错。 - **数据加载后更新**:当 `cartList` 被赋值(如 `[...data.list]`),Vue 重新渲染列表,但此时 `key` 的生成逻辑(`item.goods_id + '-' + item.goods.goods_image`)可能产生重复值。 - **Vuex Action 未定义**:`dispatch('cart/setCartList')` 报错 `unknown action type`,说明 Store 中未注册该 Action。 --- ### **关键问题** #### **1. `v-for` 的 `key` 重复** - **错误原因**: - 你的 `key` 是 `item.goods_id + '-' + item.goods.goods_image`,但不同商品的 `goods_id` 或 `goods_image` 可能相同(如多个商品使用同一图片)。 - 例如:两个商品的 `goods_id` 分别为 `10041` 和 `10042`,但 `goods_image` 相同,导致 `key` 重复。 - **解决方案**: - 使用**唯一标识**(如 `item.id` 或 `item.goods_id + '-' + index`): ```html <div v-for="(item, index) in cartList" :key="item.id || (item.goods_id + '-' + index)"> ``` #### **2. Vuex Action 未定义** - **错误原因**: - 你调用了 `this.$store.dispatch('cart/setCartList')`,但 Store 中没有 `setCartList` Action。 - 从日志看,你只有 `cart/getCartListActions`,没有 `setCartList`。 - **解决方案**: - 在 Store 中定义 `setCartList` Action: ```javascript // store/modules/cart.js actions: { setCartList(ctx, list) { ctx.commit('SET_CART_LIST', list); }, }, mutations: { SET_CART_LIST(state, list) { state.cartList = list; // 直接赋值(确保 list 是响应式) }, }, ``` #### **3. 数据加载时序问题** - **现象**: - 报错发生在数据加载前(初始渲染),但数据加载后再次渲染时才触发 `key` 重复检查。 - **原因**: - Vue 在初始渲染时不会报 `key` 重复(因为 `cartList` 为空)。 - 数据加载后,`cartList` 被赋值,Vue 重新渲染列表,此时检测到 `key` 重复。 - **解决方案**: - **加载状态控制**:在数据加载完成前不渲染列表。 ```html <template> <div v-if="isLoading">Loading...</div> <div v-else> <div class="cart-item" v-for="item in cartList" :key="item.id"> <!-- 内容 --> </div> </div> </template> ``` - **默认值初始化**:确保 `cartList` 初始值为 `[]`(避免 `undefined`)。 --- ### **完整修复代码** #### **1. 修改 `v-for` 的 `key`** ```html <div class="cart-item" v-for="(item, index) in cartList" :key="item.id || (item.goods_id + '-' + index)"> <!-- 内容 --> </div> ``` #### **2. 定义 Vuex Action** ```javascript // store/modules/cart.js actions: { async getCartListActions(ctx) { const { data } = await getCartList(); ctx.commit('SET_CART_LIST', data.list); }, setCartList(ctx, list) { ctx.commit('SET_CART_LIST', list); // 新增 Action }, }, mutations: { SET_CART_LIST(state, list) { state.cartList = list; // 直接赋值 }, }, ``` #### **3. 优化数据加载逻辑** ```javascript async created() { if (!this.userInfo.token) return; try { // 1. 获取数据 const { data } = await getCartList(); // 2. 提交到 Vuex(或直接赋值给本地状态) await this.$store.dispatch('cart/setCartList', data.list); // 3. 可选:同时更新本地状态(如需要) this.mycartList = data.list; } catch (error) { console.error('加载购物车失败:', error); } }, ``` #### **4. 添加加载状态** ```javascript data() { return { isLoading: true, }; }, async created() { if (!this.userInfo.token) { this.isLoading = false; return; } try { const { data } = await getCartList(); await this.$store.dispatch('cart/setCartList', data.list); } finally { this.isLoading = false; } }, ``` --- ### **调试步骤** 1. **检查 `key` 唯一性**: - 在渲染前打印 `cartList`,确认 `goods_id` 和 `goods_image` 是否唯一: ```javascript console.log('cartList:', this.cartList.map(item => ({ id: item.id, goods_id: item.goods_id, image: item.goods.goods_image, key: `${item.goods_id}-${item.goods.goods_image}`, }))); ``` - 如果发现重复,修改 `key` 生成逻辑。 2. **验证 Vuex Action**: - 确保 `cart/setCartList` 已注册: ```javascript console.log('Store Actions:', this.$store._actions); // 检查是否有 'cart/setCartList' ``` 3. **监控数据更新**: - 在计算属性中添加日志: ```javascript computed: { ...mapState('cart', ['cartList']), debugCartList() { console.log('计算属性触发:', this.cartList); return this.cartList; }, }, ``` --- ### **常见问题修复** #### **1. `key` 重复** - **现象**:`Duplicate keys detected`。 - **修复**: - 使用 `item.id` 或 `(item.goods_id + '-' + index)` 作为 `key`。 #### **2. Vuex Action 未定义** - **现象**:`[vuex] unknown action type: cart/setCartList`。 - **修复**: - 在 Store 中正确定义 Action 和 Mutation。 #### **3. 数据加载时序问题** - **现象**:初始渲染无数据,加载后报错。 - **修复**: - 使用 `v-if` 控制渲染时机。 --- ### **总结** | **问题类型** | **解决方案** | |----------------------|------------------------------------------------------------------------------| | `v-for` 的 `key` 重复 | 使用唯一标识(如 `item.id` 或 `index`)作为 `key`。 | | Vuex Action 未定义 | 在 Store 中正确定义 `setCartList` Action 和 Mutation。 | | 数据加载时序问题 | 使用 `v-if` 或加载状态控制渲染时机。 | --- ### **预防措施** 1. **代码规范**: - 始终为 `v-for` 提供唯一 `key`(避免依赖可能重复的字段)。 - 在调用 Vuex Action 前确认其存在。 2. **测试**: - 编写单元测试验证 `key` 的唯一性: ```javascript it('cartList 的 key 应唯一', () => { const list = wrapper.vm.cartList; const keys = list.map(item => `${item.goods_id}-${item.goods.goods_image}`); expect(new Set(keys).size).toBe(keys.length); // 无重复 }); ``` 3. **错误处理**: - 在数据加载时捕获异常,避免页面崩溃: ```javascript async created() { try { await this.loadCartData(); } catch (error) { this.$toast.error('加载购物车失败'); } }, ``` ---
评论 14
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值