当前位置: 首页 > article >正文

_computed _destinations() 为什么模板不写()

_computed _destinations() 为什么模板不写()

在 Vue 模板中使用计算属性时,不需要在属性名称后面加上括号 ()。计算属性本质上是一个 getter 函数,但 Vue 会将其转换为一个响应式的属性。Vue 会自动调用这些计算属性并将其结果作为属性值使用。

计算属性的工作原理

  1. 属性而非方法: 在 Vue 中,计算属性被定义为属性,而不是方法。这意味着你在模板中使用它们时,就像使用普通的对象属性一样,而不是调用函数。

  2. 自动计算和缓存: Vue 会自动计算计算属性的值,并在其依赖项发生变化时重新计算。计算属性的结果会被缓存,直到其依赖项发生变化。这使得计算属性在性能上比方法更高效,尤其是在涉及到复杂计算时。

示例

假设你在组件中定义了一个计算属性 destinations

computed: {
  ...mapGetters('destination', ['getAllDestinations']),
  // 计算属性本质上是一个 getter 函数,但 Vue 会将其转换为一个响应式的属性并缓存
  // 定义时是函数 
  destinations() {
    return this.getAllDestinations
  }
}

在模板中,你可以直接使用 destinations,而不需要加括号:

<template>
  <div class="destinations-grid">
    <el-card
        v-for="destination in destinations"  // 在模板中,你可以直接使用 `destinations`,而不需要加括号
                                             // computed 本质上是一个 getter 函数,但 Vue 会将其转换为一个响应式的属性。Vue 会自动调用这些计算属性并将其结果作为属性值使用。computed 的结果会缓存,直到其依赖项发生变化。
                                             // v-for="destination in destinations()" 如果写成方法调用,每次渲染都会重新执行
        :key="destination.id"
        class="destination-card"
        @click="viewDestination(destination)"
        :body-style="{ padding: '0px' }"
    >
      <img :src="destination.imageUrl" alt="" class="destination-image" />
      <div class="destination-info">
        <h3>{{ destination.name }}</h3>
        <p>{{ destination.shortDescription }}</p>
      </div>
    </el-card>
  </div>
</template>

原因解释

  1. Vue 的处理机制
    • Vue 在内部会将计算属性的函数转换为 getter/setter
    • 当访问这个属性时,Vue 自动调用 getter 函数
    • 结果会被缓存,直到依赖项改变
  2. 性能优势
    • 计算属性会缓存结果
    • 只有当依赖的响应式数据变化时才会重新计算
    • 如果写成方法调用,每次渲染都会重新执行
  3. 代码可读性
    • 不使用括号使得模板更简洁
    • 更符合"属性"的概念
    • 与普通的数据属性使用方式保持一致

这就是为什么在模板中我们直接使用 destinations 而不是 destinations()

总结

  • 不需要括号: 在模板中使用计算属性时,不需要加括号,因为它们被视为属性。
  • 自动更新: 计算属性会根据其依赖项的变化自动更新,并且结果会被缓存以提高性能。

这种设计使得 Vue 的模板语法更加简洁和直观,同时也提高了应用的性能。

getters 访问 Vuex 中的数据 为什么不直接state

让我用一个具体的例子来说明为什么使用 getters 比直接访问 state 更好:

1. 数据处理和转换

// store/index.js
export default new Vuex.Store({
  state: {
    destinations: [
      { id: 1, name: '北京', isActive: true },
      { id: 2, name: '上海', isActive: false },
      { id: 3, name: '广州', isActive: true }
    ]
  },
  getters: {
    // 获取激活的目的地
    activeDestinations: state => {
      return state.destinations.filter(dest => dest.isActive);
    }
  }
});

2. 使用对比

// 使用 getters(推荐)
export default {
  computed: {
    ...mapGetters(['activeDestinations'])
  }
}

// 直接使用 state(不推荐)
export default {
  computed: {
    activeDestinations() {
      return this.$store.state.destinations.filter(dest => dest.isActive);
    }
  }
}

使用 getters 的优势

  1. 逻辑复用

    • 如果多个组件都需要筛选激活的目的地,使用 getters 可以避免重复编写过滤逻辑
    • 一旦筛选逻辑需要修改,只需要改动 getter 中的一处代码
  2. 数据缓存

    • getters 的结果会被缓存,只有当依赖的 state 改变时才会重新计算
    • 直接在组件中过滤 state 会在每次访问时重新计算
  3. 维护性

    • 如果将来数据结构发生变化(比如 isActive 改名为 active),只需要修改 getter 中的一处代码
    • 直接访问 state 的话,需要修改所有使用这个数据的组件
  4. 集中管理

    • 所有数据处理逻辑都在 store 中,便于管理和调试
    • 避免在不同组件中散布相同的数据处理逻辑

实际应用示例

// store/modules/destination.js
export default {
  state: {
    destinations: []
  },
  getters: {
    // 获取所有目的地
    getAllDestinations: state => state.destinations,
    
    // 获取激活的目的地
    getActiveDestinations: state => {
      return state.destinations.filter(dest => dest.isActive);
    },
    
    // 按名称搜索目的地
    getDestinationsByName: state => name => {
      return state.destinations.filter(dest => 
        dest.name.toLowerCase().includes(name.toLowerCase())
      );
    }
  }
};

// 组件中使用
export default {
  computed: {
    ...mapGetters([
      'getAllDestinations',
      'getActiveDestinations'
    ])
  },
  methods: {
    searchDestinations(name) {
      // 使用带参数的 getter
      return this.$store.getters.getDestinationsByName(name);
    }
  }
}

这样的设计让代码:

  • 更容易维护
  • 更好复用
  • 更有效率
  • 更容易测试
  • 更容易调试

当你的应用变得复杂时,这些优势会变得更加明显。

在 Vuex 中使用 getters 而不是直接访问 state 有几个好处:

  1. 抽象层次
    Getters 提供了一个抽象层次,使得组件不需要直接依赖于 state 的具体结构。如果 state 的结构发生变化,只需要修改 getters,而不需要修改所有使用该 state 的组件。

  2. 复用性
    Getters 可以在多个组件中复用,尤其是当需要对 state 进行一些计算或转换时。这样可以避免在多个组件中重复相同的逻辑。

  3. 缓存
    Getters 是基于 Vue 的响应式系统的,它们会根据依赖的变化自动更新,并且在依赖不变的情况下会缓存结果,从而提高性能。

  4. 可测试性
    Getters 可以单独进行测试,因为它们是纯函数,不依赖于组件的具体实现。

尽管在简单的场景下,直接访问 state 可能看起来更直接,但在复杂应用中,使用 getters 可以带来更好的可维护性和可扩展性。

如果你的应用很简单,或者你确定 state 的结构不会改变,直接访问 state 也是可以接受的。但在大多数情况下,使用 getters 是一种更好的实践。

computed 的方法 为什么不放到 methods: {

在 Vue 中,computedmethods 都可以用来定义组件的逻辑,但它们的用途和行为是不同的,因此不能随意互换。以下是为什么 computed 的方法不能放到 methods 中的原因,以及两者的区别和适用场景。


1. computedmethods 的核心区别

1.1 computed 是基于依赖的 缓存属性
  • 基于依赖进行缓存

  • 只有当依赖项发生变化时才会重新计算

  • 返回值会被缓存,多次访问不会重复计算

  • 适合需要依赖其他数据计算得到结果的场景

  • 特点

    • computed基于依赖的响应式属性,只有当依赖的数据发生变化时,computed 的值才会重新计算。
    • 如果依赖的数据没有变化,computed 会直接返回之前缓存的值,而不会重新执行计算逻辑。
  • 适用场景

    • 当需要基于现有数据计算出一个派生值,并且这个值会被多次使用时,使用 computed 可以提高性能。
    • 例如:格式化数据、过滤列表、计算总和等。
1.2 methods 是普通的函数调用
  • 每次调用都会执行

  • 不会缓存结果

  • 适合处理事件或执行操作的场景

  • 特点

    • methods 是普通的函数,每次调用都会重新执行逻辑。
    • 它不会缓存结果,也不会自动响应依赖的变化。
  • 适用场景

    • 当需要执行某些操作或逻辑,而不是返回一个值时,使用 methods
    • 例如:事件处理、触发异步请求、手动更新数据等。

2. 为什么 computed 的方法不能放到 methods 中?

2.1 computed 的缓存机制
  • 如果将一个需要缓存的逻辑放到 methods 中,每次调用都会重新执行计算逻辑,浪费性能。
  • computed 会根据依赖的数据变化自动更新,并且在依赖未变化时直接返回缓存的值。

示例:

export default {
  data() {
    return {
      firstName: 'John',
      lastName: 'Doe'
    };
  },
  computed: {
    fullName() {
      console.log('Computed fullName is called');
      return `${this.firstName} ${this.lastName}`;
    }
  },
  methods: {
    getFullName() {
      console.log('Method getFullName is called');
      return `${this.firstName} ${this.lastName}`;
    }
  }
};

模板:

<template>
  <div>
    <p>Computed: {{ fullName }}</p>
    <p>Method: {{ getFullName() }}</p>
  </div>
</template>

结果:

  • computed
    • fullName 只会在 firstNamelastName 发生变化时重新计算。
    • 如果多次访问 fullName,只会触发一次计算,后续直接返回缓存值。
  • methods
    • 每次调用 getFullName() 都会重新执行逻辑,即使 firstNamelastName 没有变化。

2.2 响应式特性
  • computed 是响应式的,依赖的数据变化时会自动触发重新计算。
  • methods 不具备响应式特性,必须手动调用才能执行逻辑。

示例:

export default {
  data() {
    return {
      count: 0
    };
  },
  computed: {
    doubleCount() {
      return this.count * 2;
    }
  },
  methods: {
    getDoubleCount() {
      return this.count * 2;
    }
  }
};

模板:

<template>
  <div>
    <p>Computed: {{ doubleCount }}</p>
    <p>Method: {{ getDoubleCount() }}</p>
    <button @click="count++">Increment</button>
  </div>
</template>

结果:

  • computed
    • 当点击按钮时,count 的值发生变化,doubleCount 会自动更新。
  • methods
    • getDoubleCount() 不会自动更新,必须手动调用才能获取最新的值。

3. 什么时候用 computed,什么时候用 methods

3.1 使用 computed 的场景
  • 需要基于现有数据计算派生值,并且这个值会被多次使用。
  • 需要缓存结果,避免重复计算。
  • 需要响应式更新,当依赖的数据变化时,自动更新计算结果。

示例:

computed: {
  fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  filteredItems() {
    return this.items.filter(item => item.active);
  }
}

export default {  
  data() {  
    return {  
      price: 100,  
      quantity: 2,  
      discount: 0.8  
    }  
  },  
  
  computed: {  
    // 1. 基础计算  
    totalPrice() {  
      return this.price * this.quantity * this.discount;  
    },  
    
    // 2. 格式化显示  
    formattedPrice() {  
      return `¥${this.totalPrice.toFixed(2)}`;  
    },  
    
    // 3. 数据过滤  
    activeUsers() {  
      return this.users.filter(user => user.isActive);  
    },  
    
    // 4. 状态计算  
    isValid() {  
      return this.name.length > 0 && this.email.includes('@');  
    }  
  }  
}

3.2 使用 methods 的场景
  • 不需要缓存,每次调用都需要重新执行逻辑。
  • 不需要响应式更新,只在特定事件或操作中手动调用。
  • 执行操作或触发事件,而不是返回一个值。

示例:

methods: {
  fetchData() {
    // 触发异步请求
    axios.get('/api/data').then(response => {
      this.items = response.data;
    });
  },
  handleClick() {
    console.log('Button clicked');
  }
}

export default {  
  methods: {  
    // 1. 事件处理  
    handleClick() {  
      // 处理点击事件  
      this.count++;  
    },  
    
    // 2. 异步操作  
    async fetchData() {  
      const response = await api.getData();  
      this.data = response.data;  
    },  
    
    // 3. 有副作用的操作  
    saveToLocalStorage() {  
      localStorage.setItem('data', JSON.stringify(this.data));  
    },  
    
    // 4. 需要参数的方法  
    calculateTotal(tax) {  
      return this.price * this.quantity * (1 + tax);  
    }  
  }  
}


4. 总结对比

特性computedmethods
是否缓存是,依赖未变化时返回缓存值否,每次调用都会重新执行逻辑
是否响应式是,依赖的数据变化时会自动更新否,必须手动调用
适用场景计算派生值,依赖数据变化时自动更新执行操作、事件处理、逻辑不需要缓存
性能更高效,避免重复计算每次调用都重新计算,性能较低

5. 实际开发中的建议

  1. 优先使用 computed 处理派生值

    • 如果逻辑是基于现有数据计算出的值,并且需要响应式更新,优先使用 computed
    • 例如:格式化日期、计算总和、过滤列表等。
  2. 使用 methods 处理操作逻辑

    • 如果逻辑是执行某些操作(如事件处理、异步请求),或者不需要缓存和响应式更新,使用 methods
    • 例如:按钮点击事件、表单提交、数据请求等。
  3. 避免滥用 methods

    • 如果逻辑是基于数据计算出的值,但放在 methods 中,会导致性能浪费和代码不清晰。
    • 例如:将 computed 的逻辑放到 methods 中,每次调用都会重新计算,浪费性能。

computed 的高级用法

export default {
  computed: {
    // 1. getter 和 setter
    fullName: {
      get() {
        return `${this.firstName} ${this.lastName}`;
      },
      set(newValue) {
        [this.firstName, this.lastName] = newValue.split(' ');
      }
    },
    
    // 2. 多个依赖项的复杂计算
    orderSummary() {
      const subtotal = this.items.reduce((sum, item) => {
        return sum + (item.price * item.quantity);
      }, 0);
      
      const tax = subtotal * this.taxRate;
      const shipping = this.calculateShipping();
      
      return {
        subtotal,
        tax,
        shipping,
        total: subtotal + tax + shipping
      };
    },
    
    // 3. 带条件的计算
    displayStatus() {
      if (!this.isLoggedIn) return '请登录';
      if (this.isLoading) return '加载中...';
      if (this.error) return `错误: ${this.error}`;
      return '正常';
    }
  }
}

性能对比示例

export default {
  data() {
    return {
      items: [
        { id: 1, price: 100, quantity: 2 },
        { id: 2, price: 200, quantity: 1 }
        // ... 更多商品
      ]
    }
  },
  
  computed: {
    // 计算属性:结果被缓存
    total() {
      console.log('计算总价'); // 只在依赖变化时执行
      return this.items.reduce((sum, item) => {
        return sum + (item.price * item.quantity);
      }, 0);
    }
  },
  
  methods: {
    // 方法:每次调用都会重新计算
    calculateTotal() {
      console.log('计算总价'); // 每次调用都执行
      return this.items.reduce((sum, item) => {
        return sum + (item.price * item.quantity);
      }, 0);
    }
  }
}

实际应用场景

export default {
  data() {
    return {
      products: [],
      searchQuery: '',
      selectedCategory: '',
      sortOrder: 'asc'
    }
  },
  
  computed: {
    // 1. 过滤和排序商品列表
    filteredProducts() {
      return this.products
        // 先过滤
        .filter(product => {
          const matchesSearch = product.name
            .toLowerCase()
            .includes(this.searchQuery.toLowerCase());
          const matchesCategory = !this.selectedCategory || 
            product.category === this.selectedCategory;
          return matchesSearch && matchesCategory;
        })
        // 再排序
        .sort((a, b) => {
          return this.sortOrder === 'asc' 
            ? a.price - b.price 
            : b.price - a.price;
        });
    },
    
    // 2. 统计信息
    statistics() {
      const total = this.filteredProducts.length;
      const inStock = this.filteredProducts.filter(p => p.stock > 0).length;
      const outOfStock = total - inStock;
      
      return {
        total,
        inStock,
        outOfStock,
        inStockPercentage: ((inStock / total) * 100).toFixed(1)
      };
    },
    
    // 3. 表单验证状态
    formValidation() {
      return {
        isValid: this.validateAll(),
        errors: this.getValidationErrors()
      };
    }
  }
}

最佳实践

export default {
  computed: {
    // 1. 保持计算属性简单,专注于一个功能
    isFormValid() {
      return this.validateName && this.validateEmail && this.validatePassword;
    },
    
    // 2. 使用多个小的计算属性而不是一个大的
    validateName() {
      return this.name.length >= 3;
    },
    
    validateEmail() {
      return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(this.email);
    },
    
    validatePassword() {
      return this.password.length >= 6;
    },
    
    // 3. 合理使用 getter 和 setter
    userProfile: {
      get() {
        return {
          name: this.name,
          email: this.email,
          avatar: this.avatar
        };
      },
      set(profile) {
        this.name = profile.name;
        this.email = profile.email;
        this.avatar = profile.avatar;
      }
    }
  }
}

注意事项

  1. 避免在计算属性中进行异步操作
// ❌ 错误示例
computed: {
  async userInfo() {
    const response = await fetch('/api/user');
    return response.json();
  }
}

// ✅ 正确做法:使用 methods 或 watch
methods: {
  async fetchUserInfo() {
    const response = await fetch('/api/user');
    this.userInfo = await response.json();
  }
}
  1. 避免直接修改依赖项
// ❌ 错误示例
computed: {
  fullName() {
    this.firstName = this.firstName.trim(); // 不要在计算属性中修改数据
    return `${this.firstName} ${this.lastName}`;
  }
}

// ✅ 正确做法:使用 getter/setter 或 methods
computed: {
  fullName: {
    get() {
      return `${this.firstName.trim()} ${this.lastName}`;
    },
    set(value) {
      [this.firstName, this.lastName] = value.split(' ');
    }
  }
}
  1. 避免过度使用计算属性
// ❌ 不必要的计算属性
computed: {
  userName() {
    return this.user.name; // 简单属性访问不需要计算属性
  }
}

// ✅ 直接使用数据属性
template: `<div>{{ user.name }}</div>`

使用 computed 还是 methods 主要取决于:

  1. 是否需要缓存结果
  2. 是否依赖于其他数据
  3. 是否需要参数
  4. 是否包含副作用

选择合适的方式可以提高应用性能并使代码更易维护。


http://www.kler.cn/a/408657.html

相关文章:

  • (动画)Qt控件 QLCDNumer
  • Windows server2016设置多用户界面——保姆级教程
  • 从搭建uni-app+vue3工程开始
  • VMware Workstation 17.6.1
  • Stm32f103X HAL库 串口DMA空闲中断学习 踩坑记
  • 全面击破工程级复杂缓存难题
  • 渗透测试---shell(6)if条件判断与for循环结构
  • Vue小项目(开发一个购物车)
  • realme gt neo6官方刷机包 全量升级包下载
  • jar包解压和重新打包
  • 微信小程序 表单验证(async-validator)
  • 基于Gradle搭建Spring6.2.x版本源码阅读环境
  • Alluxio在小红书的实践:加速云端机器学习
  • HarmonyOS Next 浅谈 发布-订阅模式
  • 【热门主题】000062 云原生后端:开启高效开发新时代
  • IDEA运行程序》java: 程序包XX不存在
  • shell编程之awk
  • MySQL:IF()函数根据指定条件返回不同的值
  • 【ubuntu+win】Win10+Ubuntu22.04双系统给ubuntu系统中的某个分区进行扩容(从400G->800G)数据无损坏
  • Vue实训---4-使用Pinia实现menu菜单展示/隐藏
  • AWS EventBridge 和 Lambda 监控 ECS 事件并发送钉钉通知
  • Unity图形学之着色器之间传递参数
  • 《AI大模型开发笔记》——LangChain快速入门
  • 数据结构 【带环链表2】
  • spf算法、三类LSA、区间防环路机制/规则、虚连接
  • 实时数据研发|Flink关键概念,什么是无界、有界数据集,流、批?