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

Vue3 setup、计算属性、侦听器、响应式API

一、setup

一、setup 函数基础

作用:组合式 API 的入口,用于定义响应式数据、方法和生命周期钩子

执行时机:在 beforeCreate 之前调用,此时组件实例尚未创建

基本结构

export default {setup(props, context) {// 响应式数据// 方法定义// 生命周期钩子return {// 暴露给模板的内容}}
}
  • setup中访问thisundefined  


二、核心参数解析

1. props 参数

props: {title: String
},
setup(props) {// 访问 propsconsole.log(props.title)// 不要直接解构 props!const { title } = toRefs(props) // 保持响应性
}

2. context 参数

包含三个重要属性:

setup(props, { attrs, slots, emit }) {// attrs: 非 props 的属性// slots: 插槽内容// emit: 触发自定义事件function handleClick() {emit('custom-event', payload)}
}

三、<script setup> 语法糖

更简洁的编译时语法(SFC 专用):

基础用法
<script setup>
import { ref } from 'vue'const count = ref(0)
</script><template><button @click="count++">{{ count }}</button>
</template>
特性对比
特性普通 setup<script setup>
代码量需要 return 暴露自动暴露顶层绑定
TypeScript 支持需要类型断言更好的类型推断
组件注册components 选项自动注册
自定义指令directives 选项自动注册

二、computed(计算属性)

作用:创建依赖其他响应式数据的缓存计算结果

1. 基础用法

import { ref, computed } from 'vue'// 组合式 API
const count = ref(0)
const double = computed(() => count.value * 2)

2. 可写计算属性

const firstName = ref('John')
const lastName = ref('Doe')const fullName = computed({get: () => `${firstName.value} ${lastName.value}`,set: (newValue) => {[firstName.value, lastName.value] = newValue.split(' ')}
})// 修改计算属性
fullName.value = 'Jane Smith' // 自动触发 setter

3. 最佳实践

  • 用于派生状态(如过滤列表、格式显示)

  • 避免在计算属性中进行异步操作或副作用

  • 复杂计算应拆分到独立函数中


三、watch(侦听器)

作用:观察特定数据源变化,执行副作用

1. 侦听 ref 对象

const count = ref(0)// 侦听 ref
watch(count, (newVal, oldVal) => {console.log(`从 ${oldVal} 变为 ${newVal}`)
})// 立即执行
watch(count, callback, { immediate: true })

2. 侦听 reactive 对象

const state = reactive({ user: { name: 'Alice' }
})// 需要函数返回具体值
watch(() => state.user.name,(newName) => {console.log('用户名变化:', newName)}
)// 深度侦听
watch(() => state.user,(newUser) => {console.log('用户对象深度变化:', newUser)},{ deep: true }
)

3. 多源侦听

watch([refA, () => objB.prop],([newA, newB], [oldA, oldB]) => {// 处理变化}
)

四、watchEffect(副作用侦听)

作用:立即执行函数,自动追踪依赖并响应变化

1. 基础用法

const count = ref(0)
const stop = watchEffect(() => {console.log('当前值:', count.value)// 自动追踪 count.value 作为依赖
})// 停止侦听
stop()

2. 副作用清理

watchEffect((onCleanup) => {const timer = setInterval(() => {console.log('定时器运行')}, 1000)onCleanup(() => {clearInterval(timer)})
})

3. 调试选项

watchEffect(() => { /* ... */ },{flush: 'post', // 回调在 DOM 更新后触发onTrack(e) { /* 调试依赖追踪 */ },onTrigger(e) { /* 调试依赖变化 */ }}
)

核心对比

特性computedwatchwatchEffect
返回值返回计算值无返回值无返回值
执行时机惰性计算(依赖变化时)惰性执行(依赖变化时)立即执行 + 依赖变化时
主要用途派生状态响应特定数据变化自动追踪依赖的副作用
访问旧值不支持支持不支持
多源侦听不支持支持自动处理多个依赖
性能优化自动缓存需要手动配置自动依赖追踪

五、使用场景示例

1. 搜索过滤(computed)
const searchText = ref('')
const items = ref([/* ... */])const filteredItems = computed(() => {return items.value.filter(item => item.name.includes(searchText.value))
})
2. 路由参数变化(watch)
import { watch } from 'vue'
import { useRoute } from 'vue-router'const route = useRoute()watch(() => route.params.id,(newId) => {fetchData(newId)}
)
3. 自动保存(watchEffect)
const formData = reactive({ /* ... */ })// 任何字段修改后自动保存
const stopAutoSave = watchEffect(async () => {await api.saveDraft(formData)console.log('自动保存成功')
})// 组件卸载时停止
onUnmounted(stopAutoSave)

六、最佳实践

1. 优先选择
  • 需要派生状态 → computed

  • 需要旧值 → watch

  • 多个依赖/自动追踪 → watchEffect

2. 性能优化
// 避免不必要的深度侦听
watch(() => ({ ...obj }), // 创建新对象触发变化(newObj) => { /* ... */ }
)// 限制执行频率
import { debounce } from 'lodash-es'
watch(value,debounce((newVal) => {// 处理逻辑}, 300)
)
3. 组合使用
// 复杂场景组合
const userId = ref(null)
const userData = ref(null)// 自动获取用户数据
watchEffect(() => {if (!userId.value) returnfetchUser(userId.value).then(data => {userData.value = data})
})// 手动刷新
const refreshUser = () => {// 通过修改依赖触发重新执行userId.value = userId.value 
}

七、常见问题

Q1: watch 和 watchEffect 如何选择?
  • 需要明确知道哪些状态变化触发回调 → watch

  • 依赖动态变化或需要自动追踪 → watchEffect

Q2: 为什么 computed 需要返回函数?

计算属性需要保持响应性,通过函数形式确保正确访问最新值

Q3: 如何停止侦听器?
// watch 返回停止函数
const stopWatch = watch(/* ... */)
stopWatch()// watchEffect 同理
const stopEffect = watchEffect(/* ... */)
stopEffect()
Q4: 如何处理异步操作?
// watch 中处理异步
watch(source,async (newVal, oldVal, onCleanup) => {let cancelled = falseonCleanup(() => { cancelled = true })const data = await fetchData(newVal)if (!cancelled) {// 处理数据}}
)

通过合理组合使用这些 API,可以实现高效、可维护的响应式逻辑。记住:

  • computed 用于派生数据

  • watch 用于精准控制侦听目标

  • watchEffect 用于自动追踪依赖的副作用


响应式API

一、核心 API 对比表

特性refreactivetoRefstoRef
适用类型基本类型/对象对象/数组响应式对象响应式对象的单个属性
访问方式通过 .value 访问直接访问属性解构后仍保持响应式通过 .value 访问
响应性保持始终响应式始终响应式保持解构后的响应式保持源属性的响应式连接
典型使用场景独立基本值、模板引用、组件引用复杂对象状态管理解构响应式对象将 props 属性转为响应式引用
类型推断明确类型(Ref<T>)深层响应式类型将对象属性转为 Ref 类型单个属性转为 Ref 类型

二、ref 与 reactive 深度解析

1. ref 最佳实践
import { ref } from 'vue'// 基本类型
const count = ref(0)
console.log(count.value) // 0// 引用类型(依然可用,但更推荐 reactive)
const user = ref({ name: 'Alice' })
console.log(user.value.name) // 'Alice'// 模板引用
const inputRef = ref(null)

使用场景

  • 独立的基本类型值(字符串、数字、布尔值)

  • 需要重新赋值的对象(替换整个对象时保持响应性)

  • DOM 元素引用(结合模板中的 ref 属性)


2. reactive 最佳实践
import { reactive } from 'vue'const state = reactive({user: {name: 'Bob',age: 30},items: ['apple', 'banana']
})// 直接访问属性
state.user.name = 'Charlie'
state.items.push('orange')

使用场景

  • 需要深度嵌套的复杂对象

  • 需要保持引用的数据结构(如数组、Map、Set)

  • 逻辑相关的多个属性组合(如表单字段)


三、toRefs 与 toRef 核心差异

1. toRefs 使用示例
import { reactive, toRefs } from 'vue'const state = reactive({name: 'Eva',age: 25
})// 解构后保持响应性
const { name, age } = toRefs(state)name.value = 'David' // 修改会同步到源对象
console.log(state.name) // 'David'

核心特性

  • 转换整个响应式对象的所有属性为 ref

  • 保持与源对象的响应式连接

  • 常用于组合式函数返回值


2. toRef 使用示例
import { reactive, toRef } from 'vue'const state = reactive({ count: 0 })// 创建单个属性的 ref
const countRef = toRef(state, 'count')// 修改 ref 会同步源对象
countRef.value++
console.log(state.count) // 1

核心特性

  • 针对单个属性创建 ref

  • 即使源属性不存在仍可创建(需第二个参数默认值)

  • 常用于 props 属性转换


四、组合使用模式

1. 组合式函数中的标准模式
// useFeature.js
import { reactive, toRefs } from 'vue'export function useFeature() {const state = reactive({x: 0,y: 0})function update() {state.x = Math.random()state.y = Math.random()}return { ...toRefs(state), update }
}// 组件中使用
const { x, y, update } = useFeature()

2. Props 处理最佳实践
import { toRef } from 'vue'export default {props: ['userId'],setup(props) {// 保持响应性的正确方式const userId = toRef(props, 'userId')// 错误方式:直接解构会失去响应性// const { userId } = propsreturn { userId }}
}

五、常见问题解决方案

问题 1:如何选择 ref 和 reactive?

  • 简单值 → ref

  • 复杂对象 → reactive + toRefs

  • 需要重新赋值的对象 → ref

问题 2:解构响应式对象失效怎么办?

  • 使用 toRefs 包裹后再解构

  • 直接操作原对象属性

问题 3:如何确保 props 的响应性?

  • 使用 toRef(props, 'propName')

  • 避免直接解构 props


六、TypeScript 类型处理技巧

1. ref 类型标注
const count = ref<number>(0) // Ref<number>
const user = ref<User>({ name: '' }) // Ref<User>
2. reactive 类型推断
interface State {name: stringage: number
}const state = reactive<State>({name: 'Alice',age: 25
})
3. toRefs 类型保留
const state = reactive({ x: 0, y: 0 })
const { x, y } = toRefs(state) // x: Ref<number>, y: Ref<number>

七、性能优化建议

  1. 避免深层嵌套:过度使用 reactive 会导致深层响应式代理影响性能

  2. 合理使用 shallowRef:对不需要深度监听的大对象使用 shallowRef

  3. 批量更新策略

    const state = reactive({ a: 1, b: 2 })// 推荐:单次修改多个属性
    Object.assign(state, { a: 2, b: 3 })// 不推荐:多次触发更新
    state.a = 2
    state.b = 3

八、综合应用示例

<script setup>
import { reactive, ref, toRefs, toRef } from 'vue'// 基本类型使用 ref
const searchQuery = ref('')// 复杂对象使用 reactive
const pagination = reactive({page: 1,pageSize: 10,total: 0
})// 转换为 refs 供模板使用
const { page, pageSize } = toRefs(pagination)// props 处理
const props = defineProps(['initialData'])
const initialDataRef = toRef(props, 'initialData')// 方法
function nextPage() {pagination.page++
}
</script><template><input v-model="searchQuery"><div>当前页: {{ page }}每页数量: {{ pageSize }}</div><button @click="nextPage">下一页</button>
</template>

通过合理运用这些响应式 API,可以实现:

  • 更清晰的代码结构

  • 更好的类型推断(TypeScript)

  • 更高效的响应式更新

  • 更安全的 props 处理

  • 更灵活的逻辑复用

记住黄金法则:
简单值用 ref,复杂对象用 reactive,解构对象用 toRefs,处理 props 用 toRef

相关文章:

  • 【go语言】window环境从源码编译go
  • 游戏引擎学习第241天:将OpenGL VSync 和 sRGB 扩展
  • 【c++】【STL库】vector类详解
  • Unity 使用 ADB 实时查看手机运行性能
  • [linux]设置邮件发送告警功能
  • 【C++】入门基础【下】
  • 编译 C++ 报错“找不到 g++ 编译器”的终极解决方案(含 Windows/Linux/macOS)
  • 2025最新系统 Linux 教程(六)
  • HTML5 服务器发送事件 (Server-Sent Events):实现网页自动获取服务器更新
  • 第53.5讲 | 小项目实战:用 SHAP 值解释农作物产量预测模型 [特殊字符][特殊字符]
  • Next.js v15 eslint 规则配置
  • Spring Boot知识点详解
  • 27、Session有什么重⼤BUG?微软提出了什么⽅法加以解决?
  • 【基础】Node.js 介绍、安装及npm 和 npx功能了解
  • 如何快速高效学习Python?
  • 界面开发框架DevExpress XAF实践:如何在Blazor项目中集成.NET Aspire?(二)
  • (第三篇)Springcloud之Ribbon负载均衡
  • 精益数据分析(21/126):剖析创业增长引擎与精益画布指标
  • 从码云上拉取项目并在idea配置npm时完整步骤
  • 【Spring Boot】深入解析:#{} 和 ${}
  • 荣盛发展去年亏损约84.43亿元,要“过苦日子、紧日子”
  • 国家核安全局局长:我国核电进入大规模建设高峰期,在建规模超其他国家总和
  • 俄联邦安全局:俄军高级官员汽车爆炸案嫌疑人已被捕
  • 保时捷中国研发中心落户上海虹桥商务区,计划下半年投入运营
  • 驻美国使馆发言人就美方希就关税问题与中方对话答记者问
  • 美联储报告披露关税战冲击波:消费信心下降,经济担忧加深