Vue 3 核心进阶:组合式 API、响应式系统与 <script setup>

一、Vue 3 与组合式 API

Vue 3 兼容 Vue 2 中的大部分模板和选项式 API,但新项目通常推荐使用组合式 API。选项式 API 以 datamethodscomputed 等配置项组织代码;组合式 API 则在 setup() 中按功能组合响应式数据和方法,更适合大型组件和逻辑复用。

两种风格可以在 Vue 3 中共存,但不建议在同一个组件里反复混用。组合式 API 中没有组件实例 this,响应式变量和方法需要显式返回(普通 setup)或由 <script setup> 自动暴露。

二、Vue 3 快速体验

1. 组合式 API

html 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <meta charset="UTF-8">
  <title>Vue 3 组合式 API</title>
  <script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
</head>
<body>
  <div id="app">
    <p>计数:{{ count }}</p>
    <button @click="add">加一</button>
  </div>

  <script>
    const { createApp, ref } = Vue

    createApp({
      setup() {
        const count = ref(0)

        function add() {
          count.value++
        }

        // setup 返回的内容才能在模板中使用。
        return { count, add }
      }
    }).mount('#app')
  </script>
</body>
</html>

ref(0) 返回一个响应式引用对象。在 JavaScript 中需要通过 .value 读写,在模板中 Vue 会自动解包,因此直接写 {``{ count }} 即可。

2. 选项式 API

js 复制代码
import { createApp } from 'vue'

createApp({
  data() {
    return { count: 100 }
  },
  methods: {
    add() {
      this.count++
    }
  }
}).mount('#app')

选项式 API 对 Vue 2 开发者比较熟悉,Vue 3 仍然支持。新代码可以根据团队规范选择一种风格,但要保持同一个组件结构清晰。

三、使用 Vite 创建 Vue 3 项目

Vite 是现代前端构建工具。开发环境下它可以按需提供模块并快速启动,修改代码后通过 HMR(热模块替换)更新页面。生产构建时仍会对代码进行打包和优化。

Vite 需要较新的 Node.js 版本,创建项目前应查看当前模板的版本要求:

bash 复制代码
node -v
npm -v

npm config set registry https://registry.npmmirror.com
npm create vite@latest vue3-demo

cd vue3-demo
npm install
npm run dev

常用脚本来自 package.json

json 复制代码
{
  "scripts": {
    "dev": "vite",
    "build": "vite build",
    "preview": "vite preview"
  }
}

npm run dev 用于开发,npm run build 生成部署文件,npm run preview 用于本地预览构建结果。Vite 项目常见目录包括 src/main.jssrc/App.vuesrc/componentspublic;业务代码主要放在 src 中。

四、setup、ref 与 reactive

普通 setup() 是组合式 API 的入口。它执行时机接近 Vue 2 的 beforeCreatecreated,不能在其中依赖已经挂载的 DOM。普通写法必须返回模板需要的变量和方法。

ref 适合包装数字、字符串、布尔值,也可以包装对象和数组;reactive 适合直接代理对象或数组。ref 在 JavaScript 中通过 .value 操作,reactive 直接读写属性。

vue 复制代码
<template>
  <p>数字:{{ count }}</p>
  <button @click="increase">增加</button>

  <p>姓名:{{ user.name }},年龄:{{ user.age }}</p>
  <button @click="user.age++">增加年龄</button>

  <p v-for="girl in girls" :key="girl">{{ girl }}</p>
  <button @click="changeFirstGirl">修改第一项</button>
</template>

<script>
import { ref, reactive } from 'vue'

export default {
  setup() {
    const count = ref(0)
    const user = reactive({ name: 'lqz', age: 19 })
    const girls = reactive(['小王', '小李'])

    function increase() {
      count.value++
    }

    function changeFirstGirl() {
      girls[0] = '新名字'
    }

    return { count, user, girls, increase, changeFirstGirl }
  }
}
</script>

推荐使用 const 保存 refreactive 返回的对象引用。const 不允许重新绑定变量,但不影响修改响应式对象内部的数据。不要把 ref 变量整体重新赋成普通值,否则会丢失响应式引用。

五、计算属性 computed

计算属性适合表示由其他响应式数据推导出的值。它会根据依赖缓存结果,依赖不变时不会重复计算;与每次渲染都执行的方法相比,计算属性更适合过滤、拼接和统计。

vue 复制代码
<template>
  <input v-model="person.firstName" placeholder="姓">
  <input v-model="person.lastName" placeholder="名">
  <p>姓名:{{ fullName }}</p>
  <input v-model="fullName" placeholder="修改完整姓名">
</template>

<script>
import { reactive, computed } from 'vue'

export default {
  setup() {
    const person = reactive({ firstName: '', lastName: '' })

    const fullName = computed({
      get() {
        return person.firstName + person.lastName
      },
      set(value) {
        person.firstName = value.slice(0, 1)
        person.lastName = value.slice(1)
      }
    })

    return { person, fullName }
  }
}
</script>

只读计算属性可以直接传入函数;需要通过 v-model 修改时,应提供 getset。不要在计算属性中执行发送请求、修改其他状态等副作用。

六、watch 与 watchEffect

watch 需要明确指定观察源,适合监听一个或多个值变化后执行请求、日志或数据同步。监听 reactive 对象的某个属性时,通常传入返回该属性的函数。

js 复制代码
import { ref, reactive, watch } from 'vue'

const height = ref(180)
watch(height, (newValue, oldValue) => {
  console.log('身高变化:', oldValue, newValue)
})

const person = reactive({ name: 'lqz', age: 19 })
watch(() => person.name, (newValue, oldValue) => {
  console.log('姓名变化:', oldValue, newValue)
})

const sum = ref(0)
const message = ref('正常')
watch([sum, message], ([newSum, newMessage]) => {
  console.log(newSum, newMessage)
})

watchEffect 不需要手动指定依赖,它会执行回调并自动收集回调中访问的响应式数据。依赖变化时会重新执行,适合依赖较多但关系直接的副作用。

js 复制代码
import { ref, watchEffect } from 'vue'

const keyword = ref('')
const page = ref(1)

watchEffect(() => {
  console.log(`请求关键词 ${keyword.value} 的第 ${page.value} 页`)
})

需要停止监听时,watchwatchEffect 都会返回停止函数。组件卸载时 Vue 会自动停止在组件中创建的监听,但手动创建的异步任务仍需自行处理。

七、Vue 3 生命周期

Vue 3 选项式 API 的生命周期名称与 Vue 2 基本一致,但销毁阶段改为 beforeUnmountunmounted。组合式 API 使用对应的 onXxx 函数注册钩子。

Vue 2/选项式 组合式 API 常见用途
created setup() 初始化数据、准备请求
beforeMount onBeforeMount 挂载前
mounted onMounted DOM 已挂载
beforeUpdate onBeforeUpdate DOM 更新前
updated onUpdated DOM 更新后
beforeDestroy onBeforeUnmount 卸载前清理
destroyed onUnmounted 卸载完成
vue 复制代码
<script>
import {
  ref,
  onMounted,
  onBeforeUnmount
} from 'vue'

export default {
  setup() {
    const count = ref(0)
    let timerId

    onMounted(() => {
      timerId = setInterval(() => {
        count.value++
      }, 1000)
    })

    onBeforeUnmount(() => {
      clearInterval(timerId)
    })

    return { count }
  }
}
</script>

定时器、WebSocket、全局事件监听和订阅都应在卸载前清理,否则页面离开后仍可能继续执行,造成内存泄漏或重复请求。

八、在生命周期中请求后端

需要在组件创建时加载数据,可以在 setup() 中直接调用请求,也可以在 onMounted 中调用。页面需要 DOM 时应选择 onMounted;只依赖数据时放在 setupwatchEffect 中即可。

vue 复制代码
<template>
  <p v-if="loading">加载中...</p>
  <p v-else-if="error">{{ error }}</p>
  <p v-else>姓名:{{ user.name }},性别:{{ user.gender }}</p>
</template>

<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'

const user = ref({ name: '', gender: '' })
const loading = ref(true)
const error = ref('')

onMounted(async () => {
  try {
    const response = await axios.get('http://127.0.0.1:5000/info')
    user.value = response.data
  } catch {
    error.value = '用户信息加载失败'
  } finally {
    loading.value = false
  }
})
</script>

接口失败时要给用户明确反馈,并在真实项目中配置 CORS、超时和统一请求错误处理。

九、<script setup> 语法

<script setup> 是 Vue 3 单文件组件的编译期语法糖。里面声明的变量、函数和导入的组件会自动暴露给模板,不需要再写 setup()return,也不需要手动注册导入的组件。

vue 复制代码
<script setup>
import { ref } from 'vue'
import HelloWorld from './components/HelloWorld.vue'

const name = ref('lqz')
const message = '父组件传给子组件的消息'

function changeName() {
  name.value = '彭于晏'
}
</script>

<template>
  <h1>{{ name }}</h1>
  <button @click="changeName">修改名字</button>
  <HelloWorld :msg="message" />
</template>

导入组件时必须写出正确的 .vue 文件路径。使用 <script setup lang="ts"> 时,可以进一步为 props、事件和响应式变量添加 TypeScript 类型。

十、script setup 中的组件通信

1. 父传子:defineProps

vue 复制代码
<!-- Child.vue -->
<script setup>
const props = defineProps({
  msg: { type: String, required: true }
})
</script>

<template>
  <p>收到的消息:{{ props.msg }}</p>
</template>

模板中也可以直接使用 msg,但保留 props 对象有助于明确数据来源。defineProps 是编译器宏,不需要从 Vue 导入。

2. 子传父:defineEmits

vue 复制代码
<!-- Parent.vue -->
<script setup>
import { ref } from 'vue'
import ChildEditor from './ChildEditor.vue'

const message = ref('')
function receive(value) {
  message.value = value
}
</script>

<template>
  <p>子组件消息:{{ message }}</p>
  <ChildEditor @send="receive" />
</template>
vue 复制代码
<!-- ChildEditor.vue -->
<script setup>
import { ref } from 'vue'

const emit = defineEmits(['send'])
const text = ref('')

function send() {
  emit('send', text.value)
}
</script>

<template>
  <input v-model="text">
  <button @click="send">发送</button>
</template>

3. script setup 中使用 ref

模板 ref 需要先声明一个同名的 ref()。在组件挂载后,普通标签的值是 DOM 对象,组件的值是组件实例。

vue 复制代码
<script setup>
import { ref, onMounted } from 'vue'
import ChildPanel from './ChildPanel.vue'

const inputRef = ref(null)
const childRef = ref(null)

onMounted(() => {
  inputRef.value.focus()
  childRef.value.changeTitle('父组件设置的标题')
})
</script>

<template>
  <input ref="inputRef">
  <ChildPanel ref="childRef" />
</template>

子组件必须使用 defineExpose 明确公开给父组件访问的内容:

vue 复制代码
<script setup>
import { ref } from 'vue'

const title = ref('子组件标题')
function changeTitle(value) {
  title.value = value
}

defineExpose({ title, changeTitle })
</script>

<template><h2>{{ title }}</h2></template>

十一、script setup 中的插槽

Vue 3 推荐使用 v-slot# 语法。父组件传入具名模板,子组件在对应的 <slot> 位置渲染。

vue 复制代码
<!-- HelloWorld.vue -->
<template>
  <slot name="header"></slot>
  <h2>公共组件标题</h2>
  <slot name="footer"></slot>
</template>
vue 复制代码
<!-- App.vue -->
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>

<template>
  <HelloWorld>
    <template #header><h3>父组件标题</h3></template>
    <template #footer><button>父组件按钮</button></template>
  </HelloWorld>
</template>

十二、总结

  1. Vue 3 组合式 API 通过 refreactivecomputedwatch 和生命周期函数组织逻辑。
  2. ref 适合值类型,reactive 适合对象和数组;在 JavaScript 中注意 .value 的使用。
  3. 计算属性用于派生值,watchwatchEffect 用于副作用;不要把三者混为一谈。
  4. <script setup> 会自动暴露变量和组件,父子通信使用 definePropsdefineEmitsdefineExpose
  5. 定时器、请求和 DOM 操作要结合生命周期管理,并在组件卸载时清理资源。
相关推荐
张风捷特烈38 分钟前
当 AI 遇见 Flutter | 打造 500+ Widget 专属Logo
android·前端·flutter
雪芽蓝域zzs42 分钟前
Vue3 + JS + pnpm 创建项目
开发语言·javascript·ecmascript
蜡台1 小时前
Uniapp 开发H5 页面跳转拦截
前端·javascript·uni-app
IT爱学堂1 小时前
Three.js可视化企业实战WEBGL课,2023年全新WEB 3D THREEJS技术
前端·javascript·webgl
网安蟹佬霸1 小时前
WebAssembly安全攻防实战:从WASM逆向到漏洞利用
前端·安全·自动化·区块链·智能合约·wasm
Su米苏2 小时前
关于@vue-office/excel 渲染异常滚动失去内容
前端·vue.js·excel
灯澜忆梦2 小时前
【基于GO的Web开发10】gin获取HTML-Form表单提交参数
前端·后端·golang·html·gin
撑伞的鱼99372 小时前
2026年前端AI编程工具评测:Figma 还原、组件复用、跨文件联动三项对比
前端·ai编程·figma·效率工具·ai编程工具
码视野2 小时前
基于 Vue3 + Element Plus 的【微短剧剧本智能创作与分镜生产协同系统】设计与实现(含PRD/三端源码/大屏)
前端·人工智能·vue3