【Vue 3 】——setup、ref、watch
Vue 3 核心知识点全面解析
一、Vue 3 简介
Vue 3 是下一代前端框架 Vue.js 的重大版本更新,于 2020 年 9 月正式发布。它带来了许多令人兴奋的新特性和改进,旨在解决 Vue 2 在大型项目中的规模限制,并提供更好的性能。
Vue 3 的核心亮点:

- 更强的性能:重写了虚拟 DOM,优化了编译过程,使得打包体积更小,运行速度更快。
- 更好的 TypeScript 支持:Vue 3 的代码库完全使用 TypeScript 重写,提供了完美的类型推断。
- Composition API:引入了一套新的、更具弹性的 API,用于组织和复用代码逻辑,这是 Vue 3 最重要的特性之一。
- 新的内置组件:如
<Fragment>,<Teleport>,<Suspense>等,用于处理更复杂的 UI 场景。 - 按需引入:支持更好的 Tree-Shaking,未使用的功能不会被打包到最终产物中。
二、创建 Vue 3 工程
官方推荐使用 Vite 或 vue-cli 来创建项目。Vite 提供了更快的启动和热更新速度。

使用 Vite 创建项目:

# npm
npm create vite@latest my-vue-app -- --template vue
# yarn
yarn create vite my-vue-app --template vue
# pnpm
pnpm create vite my-vue-app --template vue
然后进入项目目录,安装依赖并运行:
cd my-vue-app
npm install
npm run dev
使用 Vue CLI 创建项目:
npm install -g @vue/cli
vue create my-vue-app
# 在提示中选择 Vue 3 预设
cd my-vue-app
npm run serve
三、编写 App 组件
创建项目后,主入口文件 main.js 会使用新的 createApp 工厂函数来初始化应用:
// main.js
import { createApp } from 'vue'
import App from './App.vue'
const app = createApp(App)
app.mount('#app')
根组件 App.vue 是一个典型的单文件组件 (SFC):
<!-- App.vue -->
<template>
<div>
<h1>{{ title }}</h1>
<MyComponent />
</div>
</template>
<script>
import MyComponent from './components/MyComponent.vue'
export default {
name: 'App',
components: {
MyComponent
},
data() {
return {
title: 'Welcome to My Vue 3 App!'
}
}
}
</script>
<style>
h1 {
color: #42b883;
}
</style>

四、一个简单的效果
让我们实现一个简单的计数器组件,感受 Vue 3 的响应式特性。
<template>
<div>
<p>Count is: {{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
const increment = () => {
count.value++
}
return {
count,
increment
}
}
}
</script>


vscode同时修改多列:shift+alt拖动选择列,然后输入可以在多行输入相同内容
五、Options API 与 Composition API
这是 Vue 3 中两种组织组件代码的方式。
- Options API (Vue 2 风格):通过不同的选项属性 (
data,methods,computed,watch,生命周期等) 来组织代码。逻辑关注点分散在各个选项中。
export default {
data() {
return { count: 0 }
},
methods: {
increment() { this.count++ }
},
mounted() {
console.log('Component is mounted!')
}
}
- Composition API:使用导入的函数来定义组件逻辑。它允许我们根据逻辑关注点而不是选项类型来组织代码,使得代码更易于阅读和维护,尤其是大型组件。
import { ref, onMounted } from 'vue'
export default {
setup() {
const count = ref(0)
function increment() { count.value++ }
onMounted(() => {
console.log('Component is mounted!')
})
return { count, increment }
}
}
Composition API 的优势:
- 更好的逻辑复用能力 (自定义 Composable 函数,类似 React Hooks)
- 更灵活的代码组织
- 更好的 TypeScript 支持
六、setup 概述
setup 是 Composition API 的入口和舞台。它是一个组件选项,在组件被创建之前、props 被解析之后执行。

1、数据

2、方法

return相当于把数据交出去
注意:

- 执行时机:在
beforeCreate生命周期之前执行,只执行一次。 this的用法:在setup内部,this不是该活跃实例的引用,因为setup是在组件被完全初始化之前调用的。所有对数据和函数的访问都直接通过响应式引用和上下文对象。- 参数:它接收两个参数:
props:响应式的 props 对象。context:一个普通 JavaScript 对象,暴露了三个组件属性:attrs,slots,emit。
export default {
props: {
title: String
},
setup(props, context) {
console.log(props.title) // 访问 props
console.log(context.attrs) // 等价于 this.$attrs
console.log(context.slots) // 等价于 this.$slots
console.log(context.emit) // 等价于 this.$emit
// 在这里声明响应式数据、方法、计算属性、生命周期钩子等
// 最后必须返回一个对象,该对象上的属性可以在模板中使用
return { ... }
}
}
七、setup 的返回值:return
setup 必须返回一个对象。这个对象包含所有你想要在模板中使用的属性(响应式数据、方法等)。这个对象会被合并到组件的实例上,模板可以直接访问它们。

setup() {
const message = ref('Hello World!')
const count = ref(0)
function changeMessage() {
message.value = 'Vue 3 is awesome!'
}
// 返回的对象可以在模板中使用
return {
message,
count,
changeMessage
}
}
八、setup 与 Options API
setup 和传统的 Options API (data, methods 等) 可以共存。在 setup 中返回的属性会被暴露到 this 上,并且可以被 Options API 访问。
data可以用setup的数据
但setup不可以用data的数据
最好不要混合使用
export default {
data() {
return {
dataCount: 1
}
},
setup() {
const setupCount = ref(100)
return { setupCount }
},
mounted() {
console.log(this.dataCount) // 1 (来自 Options API)
console.log(this.setupCount) // 100 (来自 setup)
}
}
然而,通常建议在同一个组件中只选择一种风格,以避免不必要的混淆。
九、setup 的语法糖 <script setup>
为了简化 setup 的使用,Vue 3 提供了 <script setup> 语法糖。这是编译时语法糖,代码更简洁。

传统写法 vs <script setup>:
<!-- 传统写法 -->
<script>
import { ref } from 'vue'
export default {
setup() {
const count = ref(0)
return { count }
}
}
</script>
<!-- 使用 <script setup> -->
<script setup>
import { ref } from 'vue'
const count = ref(0)
// 任何顶层绑定 (变量、函数导入) 都可以直接在模板中使用
</script>
拓展:配置组件名字
一、多写一个script标签

二、下载一个插件

然后配置vite.config.ts

再设置name属性

在 <script setup> 中:
- 不需要手动return模板需要使用的数据和方法。
- 引入的组件可以直接在模板中使用,无需注册。
- 使用
defineProps和defineEmits来声明props和emits。 - 使用
useSlots和useAttrs来获取slots和attrs。
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
// 定义 props 和 emits
const props = defineProps({
title: String
})
const emit = defineEmits(['change'])
const count = ref(0)
function increment() {
count.value++
emit('change', count.value)
}
</script>
<template>
<h1>{{ title }}</h1>
<MyComponent />
<button @click="increment">{{ count }}</button>
</template>
十、ref 创建:基本类型的响应式数据
ref 函数用于创建一个响应式的引用。它接收一个内部值(可以是基本类型或对象),返回一个响应式的、可更改的 ref 对象。这个对象只有一个 .value 属性,指向其内部值。
主要用于处理基本类型数据 (String, Number, Boolean, etc.)。
import { ref } from 'vue'
const count = ref(0) // 创建一个值为 0 的响应式 ref
console.log(count.value) // 0
count.value++ // 在 JavaScript 中需要通过 .value 来访问和修改
console.log(count.value) // 1
在模板中(<template>),Vue 会自动解包,无需使用 .value:
在<script>里ref包裹的数据要.value

<template>
<div>{{ count }}</div> <!-- 直接使用,不需要 .value -->
</template>
十一、reactive 创建:对象类型的响应式数据
reactive 函数用于创建一个响应式的对象。它接收一个普通对象,返回该普通对象的响应式代理。

主要用于处理对象或数组等引用类型数据。
import { reactive } from 'vue'
const state = reactive({
count: 0,
user: {
name: 'Alice',
age: 30
},
hobbies: ['reading', 'music']
})
// 访问和修改
console.log(state.count) // 0 (直接访问属性,不需要 .value)
state.count++
state.user.name = 'Bob'
state.hobbies.push('coding')


小技巧:
选中按括号可以直接包裹

十二、ref 创建:对象类型的响应式数据
ref 也可以用来创建对象类型的响应式数据。当你使用 ref(null) 或 ref({ ... }) 时,内部会调用 reactive 方法来使值具有响应性。

ref 里的对象 其实还是用reactive 实现的
使用ref定义需要.value

注意:reactive 返回的是原始对象的 Proxy,它们是不相等的: console.log(state === originalObject) // false

import { ref } from 'vue'
const state = ref({
count: 0,
user: {
name: 'Alice'
}
})
// 在 JS 中访问和修改需要使用 .value
console.log(state.value.count) // 0
state.value.count = 1
state.value.user.name = 'Bob'
在模板中同样会自动解包:
<template>
<div>{{ state.count }}</div> <!-- 自动解包,无需 .value -->
</template>
十三、ref 对比 reactive

| 特性 | ref |
reactive |
|---|---|---|
| 数据类型 | 基本类型、对象类型 | 仅对象类型 (Array, Object, Map, Set) |
| JS 中访问 | 需要 .value |
直接访问 |
| 模板中访问 | 自动解包,无需 .value |
直接访问 |
| 重新赋值 | 可以用 .value 整体替换 |
不能直接整体替换,会失去响应性 |
| TypeScript | 需用 Ref<T> 类型 |
直接用接口类型即可 |
| 适用场景 | 基本类型数据、可能需要整体替换的引用数据 | 不需要整体替换的复杂对象、JSON 数据 |
如何选择?
- 推荐:优先使用
ref,因为它更统一,可以处理所有类型。 - 当你确定一个对象永远不会被整体替换,只是想深度响应式地更新其属性时,可以使用
reactive。
拓展:区别中的第一个
ref使用插件

在设置中√上就可以了

他就会自动补全value

区别中的第二个
不能重新分配对象,因为他就不是原来的那个对象了

解决方法


对于ref是可以直接修改的,因为.value就是响应式数据

十四、toRefs 与 toRef
不加toRefs,数据不会改变

正确写法

当你想解构一个响应式对象而不丢失其响应性时,这两个工具函数非常有用。
toRefs: 将一个响应式对象转换为一个普通对象,这个普通对象的每个属性都是指向源对象相应属性的 ref。
import { reactive, toRefs } from 'vue'
const state = reactive({
count: 0,
name: 'Vue'
})
// 直接解构会失去响应性!
// let { count, name } = state
// 使用 toRefs 解构,保持响应性
let { count, name } = toRefs(state)
// 现在 count 和 name 都是 ref,需要 .value
count.value++ // state.count 也会变成 1
toRef: 为源响应式对象上的某个属性创建一个 ref。这个 ref 是响应式的,并且会保持和源属性的同步。
import { reactive, toRef } from 'vue'
const state = reactive({
count: 0
})
const countRef = toRef(state, 'count')
// countRef 是一个 ref,修改它的 .value 会更新 state.count
countRef.value++
console.log(state.count) // 1
toref是单独解构,torefs是全部解构
解构之后原来的reactive就会变成ref对象,就需要.value

<script setup> 中解构 props 的经典用法:
<script setup>
import { toRefs } from 'vue'
const props = defineProps({
title: String,
user: Object
})
// 解构 props,保持响应式连接
const { title, user } = toRefs(props)
// 或者只解构其中一个
const titleRef = toRef(props, 'title')
</script>
十五、computed 计算属性
基本使用方法

2、computed是有缓存的,但是方法函数没有

全名打印了多次,但1只打印了一次,只有发生改变是才会重新计算

computed 函数用于创建计算属性,它接收一个 getter 函数,返回一个只读的响应式 ref 对象。

import { ref, computed } from 'vue'
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
console.log(doubleCount.value) // 0
count.value++
console.log(doubleCount.value) // 2
可写的计算属性: 传入一个带有 get 和 set 函数的对象。


const firstName = ref('John')
const lastName = ref('Doe')
const fullName = computed({
get() {
return `${firstName.value} ${lastName.value}`
},
set(newValue) {
[firstName.value, lastName.value] = newValue.split(' ')
}
})
fullName.value = 'Jane Smith' // 会触发 setter
console.log(firstName.value) // 'Jane'
console.log(lastName.value) // 'Smith'
十六、watch 监视
watch 函数用于在响应式数据变化时执行副作用操作。

情况一:监视单个 ref
不用.value,因为他监视的是这个响应式数据,不是值
import { ref, watch } from 'vue'
const count = ref(0)
watch(count, (newValue, oldValue) => {
console.log(`count changed from ${oldValue} to ${newValue}`)
})
结束监视就去调他的返回值

情况二:监视ref定义的对象数据类型


实际开发中一班不管旧数据

拓展:

情况三:监视 reactive 对象
直接监视整个 reactive 对象会默认开启深度监视,且 newValue 和 oldValue 会是同一个对象(Proxy)。
const state = reactive({ count: 0, user: { name: 'A' } })
watch(state, (newValue, oldValue) => {
// 深度监视已开启,state.user.name 变化也会触发
console.log('state changed:', newValue)
console.log(newValue === oldValue) // true (都是 Proxy)
})
情况四:监视 reactive 对象的某个属性

如果需要监视嵌套属性,需要使用 getter 函数。

错误写法:

正确写法

const state = reactive({ count: 0, user: { name: 'A' } })
// 监视一个嵌套属性
watch(
() => state.user.name,
(newName, oldName) => {
console.log(`Name changed: ${oldName} -> ${newName}`)
}
)
// 监视多个嵌套属性
watch(
[() => state.count, () => state.user.name],
([newCount, newName], [oldCount, oldName]) => {
console.log('Count or Name changed')
}
)


情况五:立即执行与深度监视

watch 的第三个参数是一个配置对象:
immediate: true:在侦听器创建时立即触发回调。deep: true:强制深度遍历源,即使它是 ref 或 getter 返回的对象。
const state = reactive({ user: { name: 'Unknown' } })
watch(
() => state.user,
(newUser) => {
console.log('User changed:', newUser)
},
{ immediate: true, deep: true }
// 注意:这里监视的是 state.user 这个对象引用。
// 如果不加 deep: true,修改 state.user.name 不会触发回调,因为对象引用没变。
)
十七、watchEffect
watchEffect 会立即执行传入的函数,同时会自动追踪该函数依赖的所有响应式属性,并在它们发生变化时重新运行该函数。

- 无需明确指定监视源,自动收集依赖。
- 立即执行。
- 没有
newValue和oldValue。

import { ref, watchEffect } from 'vue'
const count = ref(0)
const name = ref('Alice')
watchEffect(() => {
// 这个函数用到了 count 和 name,它们都是依赖
console.log(`Effect triggered: Count is ${count.value}, Name is ${name.value}`)
})
// 立即打印: "Effect triggered: Count is 0, Name is Alice"
count.value++
// 重新执行,打印: "Effect triggered: Count is 1, Name is Alice"
停止侦听器: watch 和 watchEffect 都会返回一个停止函数,调用它可以手动停止侦听。
const stop = watchEffect(() => { ... })
// 当不再需要此侦听器时:
stop()
副作用清除: watchEffect 的回调函数可以接收一个 onCleanup 函数作为参数,用于注册清理回调。
watchEffect((onCleanup) => {
const timer = setTimeout(() => {
// 做一些异步操作
}, 1000)
// 在下次重新执行前,或停止侦听时,会先调用这个函数
onCleanup(() => {
clearTimeout(timer)
})
})
watch vs watchEffect:
watch:需要明确指定监视源,可以访问变化前后的值,惰性(除非设置immediate)。watchEffect:自动收集依赖,无法获取旧值,立即执行。常用于处理不需要知道前后值的副作用。
总结
Vue 3 的 Composition API 通过 ref, reactive, computed, watch, watchEffect 等函数,为我们提供了更强大、更灵活的方式来组织和复用组件逻辑。结合 <script setup> 语法糖,代码变得非常简洁和直观。理解这些核心概念是掌握 Vue 3 开发的关键。建议在实际项目中多多练习,以加深理解和运用。
更多推荐



所有评论(0)