一、组合式 API(Composition API)核心

组合式 API 是 Vue3 最核心的特性,解决了 Vue2 选项式 API 中代码逻辑分散、大型组件难以维护的问题。所有代码都写在 <script setup> 语法糖中,更简洁高效。

1. ref:定义基础类型响应式数据

ref 用于定义字符串、数字、布尔值等基础数据类型或者对象类型的响应式变量,在 JS 中访问 / 修改需要加 .value,模板中直接使用。

案例:计数器

<template>
  <div>
    <p>计数:{{ count }}</p>
    <button @click="addCount">+1</button>
  </div>
</template>

<script setup>
// 1. 导入 ref
import { ref } from 'vue'

// 2. 定义响应式数据
const count = ref(0)

// 3. 定义修改数据的方法
const addCount = () => {
  // JS 中操作 ref 数据必须加 .value
  count.value++
}
</script>

2. reactive:定义引用类型响应式数据

reactive 用于定义对象、数组等引用类型的响应式变量,无需加 .value,直接操作属性即可。

案例:用户信息展示与修改

<template>
  <div>
    <p>姓名:{{ user.name }}</p>
    <p>年龄:{{ user.age }}</p>
    <button @click="updateAge">修改年龄</button>
  </div>
</template>

<script setup>
import { reactive } from 'vue'

// 定义响应式对象
const user = reactive({
  name: 'Vue3初学者',
  age: 20
})

// 修改对象属性
const updateAge = () => {
  user.age++
}
</script>

3. computed:计算属性

依赖响应式数据自动计算新值,具有缓存特性,依赖数据不变时不会重复计算。

案例:总价计算

<template>
  <div>
    <p>单价:{{ price }} 元</p>
    <p>数量:{{ num }}</p>
    <p>总价:{{ totalPrice }} 元</p>
  </div>
</template>

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

const price = ref(10)
const num = ref(2)

// 计算总价:依赖 price 和 num 自动更新
const totalPrice = computed(() => {
  return price.value * num.value
})
</script>

4. watch:侦听器

监听响应式数据变化,数据改变时执行自定义逻辑(支持异步操作)。

案例:监听计数变化

<template>
  <div>
    <p>计数:{{ count }}</p>
    <button @click="count++">+1</button>
  </div>
</template>

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

const count = ref(0)

// 监听 count 变化
watch(count, (newVal, oldVal) => {
  console.log(`计数从 ${oldVal} 变为 ${newVal}`)
  // 可执行异步请求、DOM 操作等
})
</script>

5. 生命周期函数

Vue3 组合式 API 中,生命周期以钩子函数形式使用,常用:

  • onMounted:组件挂载完成(DOM 渲染完毕,适合发请求、操作 DOM)
  • onUpdated:组件更新完成
  • onUnmounted:组件卸载(适合清除定时器、解绑事件)

案例:定时器 + 生命周期清理

<template>
  <div>秒数:{{ timer }}</div>
</template>

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

const timer = ref(0)
let interval = null

// 组件挂载后开启定时器
onMounted(() => {
  interval = setInterval(() => {
    timer.value++
  }, 1000)
})

// 组件卸载时清除定时器(防止内存泄漏)
onUnmounted(() => {
  clearInterval(interval)
})
</script>

二、父子组件通信(defineProps + defineEmits)

组件化开发中,父传子用 defineProps ,子传父用 defineEmits,是 Vue3 组件通信的基础。

1. 父传子:defineprops 接收数据

子组件通过 defineProps 声明接收父组件传递的数据,支持类型校验。

父组件(Parent.vue)

<template>
  <div>
    <h3>父组件</h3>
    <!-- 向子组件传递数据 -->
    <Child :msg="parentMsg" :list="parentList" />
  </div>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const parentMsg = ref('我是父组件的数据')
const parentList = ref(['Vue3', 'Pinia', 'Vite'])
</script>

子组件(Child.vue)

<template>
  <div>
    <h4>子组件</h4>
    <p>接收父组件消息:{{ msg }}</p>
    <p>接收列表:{{ list }}</p>
  </div>
</template>

<script setup>
// 子组件声明接收 props,自带类型校验
const props = defineProps({
  msg: String,
  list: Array
})
</script>

2. 子传父:defineEmits 触发事件

子组件通过 defineEmits 定义事件,触发事件向父组件传递数据;父组件监听事件接收数据。

子组件(Child.vue)

<template>
  <button @click="sendToParent">向父组件传值</button>
</template>

<script setup>
// 1. 定义可触发的事件
const emit = defineEmits(['child-event'])

const sendToParent = () => {
  // 2. 触发事件,传递数据
  emit('child-event', '我是子组件传递的内容')
}
</script>

父组件(Parent.vue)

<template>
  <Child @child-event="getChildData" />
  <p>接收子组件数据:{{ childData }}</p>
</template>

<script setup>
import { ref } from 'vue'
import Child from './Child.vue'

const childData = ref('')

// 接收子组件传递的数据
const getChildData = (val) => {
  childData.value = val
}
</script>

三、Pinia:Vue3 官方状态管理库

Pinia 替代 Vuex 成为 Vue3 专属状态管理,语法更简洁、支持 TypeScript、无模块化嵌套,核心:defineStore 定义仓库,state 存数据,actions 改数据。

1. 安装 Pinia

运行

npm install pinia

2. 全局注册(main.js)

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

const app = createApp(App)
app.use(createPinia()) // 注册 Pinia
app.mount('#app')

3. 定义仓库(stores/counter.js)

// 1. 导入 defineStore
import { defineStore } from 'pinia'

// 2. 定义仓库:第一个参数是仓库唯一 ID
export const useCounterStore = defineStore('counter', {
  // 状态数据(类似 data)
  state: () => ({
    count: 0
  }),
  
  // 修改状态的方法(类似 methods,支持异步)
  actions: {
    addCount() {
      this.count++
    },
    reduceCount() {
      this.count--
    }
  }
})

4. 组件使用 Pinia

<template>
  <div>
    <p>Pinia 计数:{{ counterStore.count }}</p>
    <button @click="counterStore.addCount">+1</button>
    <button @click="counterStore.reduceCount">-1</button>
  </div>
</template>

<script setup>
// 导入仓库
import { useCounterStore } from '@/stores/counter'

// 实例化仓库
const counterStore = useCounterStore()
</script>

补充 1:Pinia Action 异步写法

Action 支持 async/await 处理异步逻辑(如接口请求),和普通函数用法一致,是项目中最常用的场景。

场景:请求用户列表并更新状态

// stores/user.js
import { defineStore } from 'pinia'

export const useUserStore = defineStore('user', {
  state: () => ({
    userList: [],
    loading: false
  }),
  actions: {
    // 异步 Action,支持 async/await
    async fetchUserList() {
      this.loading = true
      try {
        // 模拟接口请求(实际项目替换为 axios 请求)
        const res = await fetch('https://jsonplaceholder.typicode.com/users')
        const data = await res.json()
        this.userList = data
      } catch (err) {
        console.error('请求失败', err)
      } finally {
        this.loading = false
      }
    }
  }
})

组件中使用

<template>
  <div>
    <button @click="userStore.fetchUserList" :disabled="userStore.loading">
      {{ userStore.loading ? '加载中...' : '获取用户列表' }}
    </button>
    <ul>
      <li v-for="user in userStore.userList" :key="user.id">{{ user.name }}</li>
    </ul>
  </div>
</template>

<script setup>
import { useUserStore } from '@/stores/user'
const userStore = useUserStore()
</script>

补充 2:Pinia storeToRefs 方法

直接解构 Pinia 仓库数据会丢失响应式storeToRefs 可以安全地把仓库中的 stategetters 转为响应式变量,方便模板中直接使用。

错误写法(丢失响应式)

// 错误:解构后 count 不是响应式,页面不会自动更新
const counterStore = useCounterStore()
const { count } = counterStore

正确写法(使用 storeToRefs)

<template>
  <div>
    <p>计数:{{ count }}</p>
    <button @click="addCount">+1</button>
  </div>
</template>

<script setup>
import { storeToRefs } from 'pinia'
import { useCounterStore } from '@/stores/counter'

const counterStore = useCounterStore()

// 1. 使用 storeToRefs 解构 state/getters,保持响应式
const { count } = storeToRefs(counterStore)

// 2. actions 可以直接解构(不会丢失响应式)
const { addCount } = counterStore
</script>

补充 3:Pinia 数据持久化

Pinia 默认数据存在内存中,刷新页面会丢失。通过 pinia-plugin-persistedstate 插件可以实现数据持久化(存入 localStorage/sessionStorage)。

步骤 1:安装插件

运行

npm install pinia-plugin-persistedstate
步骤 2:全局注册插件(main.js)

js

import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'

const pinia = createPinia()
pinia.use(piniaPluginPersistedstate) // 注册持久化插件

const app = createApp(App)
app.use(pinia)
app.mount('#app')
步骤 3:在仓库中开启持久化

js

// stores/counter.js
import { defineStore } from 'pinia'

export const useCounterStore = defineStore('counter', {
  state: () => ({
    count: 0
  }),
  actions: {
    addCount() {
      this.count++
    }
  },
  // 开启持久化配置
  persist: {
    key: 'counter-key', // 存入 localStorage 的 key 名,不写默认用仓库 ID
    storage: localStorage, // 可选:localStorage / sessionStorage
    paths: ['count'] // 只持久化指定字段,不写则持久化全部 state
  }
})

效果:修改 count 后刷新页面,数据不会丢失,会自动从 localStorage 中恢复。

更多推荐