Vue 3 核心知识点全面解析

一、Vue 3 简介

Vue 3 是下一代前端框架 Vue.js 的重大版本更新,于 2020 年 9 月正式发布。它带来了许多令人兴奋的新特性和改进,旨在解决 Vue 2 在大型项目中的规模限制,并提供更好的性能。

Vue 3 的核心亮点:

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

二、创建 Vue 3 工程

官方推荐使用 Vitevue-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 风格):通过不同的选项属性 (datamethodscomputedwatch生命周期 等) 来组织代码。逻辑关注点分散在各个选项中。
    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 是在组件被完全初始化之前调用的。所有对数据和函数的访问都直接通过响应式引用和上下文对象。
  • 参数:它接收两个参数:
    1. props:响应式的 props 对象。
    2. context:一个普通 JavaScript 对象,暴露了三个组件属性:attrsslotsemit
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

可写的计算属性: 传入一个带有 getset 函数的对象。

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 对象会默认开启深度监视,且 newValueoldValue 会是同一个对象(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"

停止侦听器: watchwatchEffect 都会返回一个停止函数,调用它可以手动停止侦听。

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 开发的关键。建议在实际项目中多多练习,以加深理解和运用。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐