阿卡不拉阿卡不拉
Vue3
阿卡的博客
Vue3
阿卡的博客
  • Vue3

    • 快速入门

      • 搭建工程 👌
      • 模板语法
      • 响应式基础
      • 响应式常用 API
      • 计算属性
      • 类与样式绑定
      • 条件和列表渲染
      • 事件处理
      • 表单处理
      • 生命周期
      • 侦听器
      • 组件介绍
      • Props
      • 自定义事件
      • 组件v-model
      • 插槽
      • 前端路由介绍
      • KeepAlive内置组件
      • 状态管理库
      • 组件库介绍
    • 深入本质

      • 虚拟DOM本质
      • 模板的本质
      • 组件树和虚拟DOM树
      • 数据拦截的本质
      • 响应式数据的本质
      • 响应式的本质
      • 响应式和组件渲染
      • 实现响应式系统 1
      • 实现响应式系统 2
      • 图解EFFECT
      • 实现响应式系统 3
      • 手写computed
      • 手写watch
      • 指令的本质
      • 插槽的本质
      • v-model的本质
      • setup 语法标签
      • 组件生命周期
      • keepalive 生命周期
      • keepalive的本质
      • key的本质
    • 细节补充

      • 【Vue】属性透传
      • 【Vue】依赖注入
      • 【Vue】组合式函数 👌
      • 【Vue】自定义指令
      • 【Vue】插件
      • 【Vue】Transition
      • 【Vue】TransitionGroup
      • 【Vue】Teleport
      • 【Vue】异步组件
      • 【Vue】Suspense
      • 【Router】路由模式
      • 【Router】路由零碎知识
      • 【Router】路由匹配语法
      • 【Router】路由组件传参
      • 【Router】内置组件和函数
      • 【Router】导航守卫
      • 【Router】过渡特效
      • 【Router】滚动行为
      • 【Router】动态路由
      • 【State】通信方式总结
      • 【State】Pinia 自定义插件
      • 【场景】封装树形组件
      • 【场景】自定义 ref 实现防抖
      • 【场景】懒加载
      • 【场景】虚拟列表
      • 【场景】虚拟列表优化
      • 【第三方库】VueUse
      • 【第三方库】vuedragable
      • 【第三方库】vue-drag-resize
      • 【第三方库】vue-chartjs
      • 【第三方库】vuelidate
      • 【第三方库】vue3-lazyload
      • 【场景】Websocket 聊天室
      • 【Vite】✨ 认识 Vite👌
      • 【Vite】配置文件 👌
      • 【Vite】✨ 依赖预构建 👌
      • 【Vite】构建生产版本 👌
      • 【Vite】环境变量与模式
      • 【Vite】CLI
      • 【Vite】Vite 插件
  • 笔面试

    • HTML

      • HTML 面试题汇总
      • 文档声明
      • 语义化
      • W3C 标准组织
      • SEO
      • iframe
      • 微格式
      • 替换元素
      • 页面可见性
    • CSS

      • CSS 面试题汇总
      • CSS 单位总结
      • 居中方式总结
      • 隐藏元素方式总结
      • 浮动
      • 定位总结
      • BFC
      • CSS 属性计算过程
      • CSS 层叠继承规则总结
      • @import 指令
      • CSS3 calc 函数
      • CSS3 媒体查询
      • 过渡和动画事件
      • 渐进增强和优雅降级
      • CSS3 变形
      • 渐进式渲染
      • CSS 渲染性能优化
      • 层叠上下文
      • CSS3 遮罩
    • JavaScript

      • JavaScript 面试题汇总
      • ✨ let、var、const 的区别
      • JS中的数据类型
      • 包装类型
      • 数据类型的转换
      • 运算符
      • ✨ 原型链
      • ✨ this 指向
      • ✨ 垃圾回收与内存泄漏
      • ✨ 执行栈和执行上下文
      • ✨ 作用域和作用域链
      • ✨ 闭包
      • DOM 事件的注册和移除
      • DOM 事件的传播机制
      • 阻止事件默认行为
      • 递归
      • ✨ 属性描述符
      • class 和构造函数区别
      • 浮点数精度问题
      • 严格模式
      • ✨ 函数防抖和节流
      • ✨ WeakSet 和 WeakMap
      • ✨ 深浅拷贝
      • 函数柯里化
      • Node 事件循环
      • 尺寸和位置
    • 浏览器

      • 浏览器面试题汇总
      • ✨ 浏览器的渲染流程
      • ✨ 资源提示关键词
      • 浏览器的组成部分
      • IndexedDB
      • ✨ File API
      • ✨ 浏览器缓存
      • ✨ 浏览器跨标签页通信
      • Web Worker
    • 网络

      • 网络面试题汇总
      • 五层网络模型 👌
      • 常见请求方法 👌
      • ✨cookie👌
      • 面试题
      • 加密
      • ✨JWT👌
      • ✨ 同源策略及跨域问题 👌
      • 文件上传
      • ✨ 输入 url 地址之后
      • 文件下载
      • ✨ session
      • ✨ TCP
      • ✨ CSRF 攻击
      • ✨XSS 攻击 👌
      • ✨ 网络性能优化
      • 断点续传
      • 域名和 DNS
      • SSL、TLS、HTTPS 的关系
      • ✨ HTTP 各版本差异 👌
      • HTTP 缓存协议
      • ✨ WebSocket
    • 工程化

      • CMJ 和 ESM
      • npx
      • ESLint
    • Vue2

      • Vue 面试题汇总相关
      • 组件通信方式总结
      • 虚拟 DOM
      • v-model
      • 数据响应式原理
      • diff
      • 生命周期详解
      • computed
      • 过滤器
      • 作用域插槽
      • 过度和动画
      • 优化
      • keep-alive
      • 长列表优化
      • 其他 API
      • 模式和环境变量
      • 更多配置
      • 更多命令
      • 嵌套路由
      • 路由切换动画
    • Vue3

      • ✨ Vue3 整体变化 👌
      • ✨ Vue3 响应式变化 👌
      • ✨ nextTick 实现原理 👌
      • 两道代码题 👌
      • Vue 运行机制
      • 渲染器核心功能
      • 事件绑定与更新

计算属性

模板表达式:

<template>
<span>Name: {{ author.name }}</span>
  <p>Has published books:</p>
  <span>{{ author.books.length > 0 ? 'Yes' : 'No' }}</span>
</template>

<script setup>
import { reactive } from 'vue'
const author = reactive({
  name: 'John Doe',
  books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', 'Vue 4 - The Mystery']
})
</script>

<style lang="scss" scoped></style>

在上面的例子中,我们之所以要使用模板表达式,是因为要渲染的数据和模板之间,并非简单的对应关系,需要进行二次处理之后,才能够在模板上使用。

虽然在上面的例子中,使用模板表达式能够解决上面的需求(对数据做二次处理的需求),但是也存在一些问题:

  1. 因为只能写表达式,不能够写语句,所以这意味着无法支持复杂的运算
  2. 将计算逻辑写在模板里面,模板显得非常的臃肿
  3. 相同的计算逻辑要在模板中出现多次,难以维护,计算逻辑理应是能够复用的

因此,正因为有上面的这些问题,所以计算属性登场了。

基本使用

基本使用代码如下:

<template>
  <span>Name: {{ author.name }}</span>
  <p>Has published books:</p>
  <span>{{ isPublishBook }}</span>
</template>

<script setup>
import { reactive, computed } from 'vue'
const author = reactive({
  name: 'John Doe',
  books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', 'Vue 4 - The Mystery']
})

const isPublishBook = computed(() => {
  // 在计算属性里面,我们就对数据进行二次处理
  return author.books.length > 0 ? 'Yes' : 'No'
})

// 计算属性也是响应式,当依赖的数据发生变化,那么计算属性也会重新计算
setTimeout(() => {
  author.books = []
}, 2000)
</script>

<style lang="scss" scoped></style>

总结一下,计算属性一般就是对响应式数据进行二次计算,返回一个计算属性的 ref,该 ref 可以在模板中使用。如果所依赖的响应式数据发生了变化,那么该计算属性会重新进行计算。

可写计算属性

一般来讲,计算属性就是对某个响应式数据进行二次计算,之后在模板中读取计算属性的值,这是绝大多数的场景下的使用,因此计算属性默认也就是只读模式。

但是,计算属性是支持可写的模式,需要往 computed 方法中传递一个对象,该对象中有对应的 getter 和 setter:

<template>
  <span>name:{{ fullName }}</span>
</template>

<script setup>
import { ref, computed } from 'vue'
const firstName = ref('Xie')
const lastName = ref('Jie')

const fullName = computed({
  // 在读取计算属性值的时候会触发
  get() {
    return firstName.value + ' ' + lastName.value
  },
  // 在设置计算属性值的时候会触发
  set(newName) {
    ;[firstName.value, lastName.value] = newName.split(' ')
  }
})

// 接下来可能因为某种原因,要设置计算属性
setTimeout(() => {
  // 这里就涉及到了设置计算属性
  fullName.value = 'Zhang San'
}, 3000)
</script>

<style lang="scss" scoped></style>

最佳实践

  1. Getter 不应该有副作用

现实生活中,也有使用“副作用”这个词的场景。

现实生活中的副作用指的就是“不期待的效果,但是它发生了”。

程序中的副作用也是类似的意思:

function effect() {
    document.body.innerText = "hello";
}

在上面的例子中,effect 函数内部修改了 document.body 的值,这就直接或者间接影响了其他函数(可能也需要读取 document.body 的值)的执行结果,这个时候我们就称 effect 函数是有副作用。

再比如,一个函数修改了全局变量,也是一个副作用操作:

let val = 1;
function effect() {
    val = 2; // 修改全局变量,产生副作用
}

常见的副作用(改变外部系统的状态或读取外部系统的状态)操作还有很多:

  • 调用系统 I/O 的 API
  • 发送网络请求
  • 在函数体内修改外部变量的值
  • 使用 console.log 等方法进行输出
  • 调用存在副作用的函数

回到 Vue 中的计算属性,一个计算属性的声明中应该描述的是根据一个值派生另外一个值,不应该改变其他的状态,也不应该在 getter 中做诸如异步请求、更改 DOM 一类的操作。

  1. 避免直接修改计算属性的值

绝大多数场景下,都应该是读取计算属性的值,而非设置计算属性的值。

从计算属性返回的值是派生状态。可以把它看作是一个“临时快照”,每当源状态发生变化时,就会创建一个新的快照。更改快照是没有意义的,因此计算属性的返回值应该被视为只读的,并且永远不应该被更改——应该更新它所依赖的源状态以触发新的计算。

计算属性和方法

除了计算属性以外,我们还可以定义方法:

<template>
  <span>Name: {{ author.name }}</span>
  <p>Has published books:</p>
  <span>{{ isPublishBook() }}</span>
</template>

<script setup>
import { reactive } from 'vue'
const author = reactive({
  name: 'John Doe',
  books: ['Vue 2 - Advanced Guide', 'Vue 3 - Basic Guide', 'Vue 4 - The Mystery']
})

function isPublishBook() {
  return author.books.length > 0 ? 'Yes' : 'No'
}
</script>

<style lang="scss" scoped></style>

计算属性依赖于响应式数据,然后对响应式数据进行二次计算。只有在响应式数据发生变化的时候,才会重新计算,换句话说,计算属性会缓存所计算的值。

而方法在重新渲染的时候,每次都是重新调用。

<template>
  <button v-on:click="a++">A++</button>
  <button v-on:click="b++">B++</button>
  <p>computedA: {{ computedA }}</p>
  <p>computedB: {{ computedB }}</p>
  <p>methodA: {{ methodA() }}</p>
  <p>methodB: {{ methodB() }}</p>
</template>

<script setup>
import { ref, computed } from 'vue'
const a = ref(1)
const b = ref(1)
// 创建两个计算属性,分别依赖 a 和 b
const computedA = computed(() => {
  console.log('计算属性A重新计算了')
  return a.value + 1
})
const computedB = computed(() => {
  console.log('计算属性B重新计算了')
  return b.value + 1
})
function methodA() {
  console.log('methodA执行了')
  return a.value + 1
}
function methodB() {
  console.log('methodB执行了')
  return b.value + 1
}
</script>

<style lang="scss" scoped></style>

最佳实践,当需要对数据进行二次计算的时候,就是使用计算属性即可。方法一般是和事件相关联,作为事件的事件处理方法来使用。

最近更新:: 2025/7/9 07:29
Contributors: AK
Prev
响应式常用 API
Next
类与样式绑定