目录

Vue3的基本使用

Vue3的基本使用

1. vue3 的基本简介

Vue.js 3Vue 框架的重大版本升级,专注于性能提升、开发体验优化和更好的 TypeScript 支持。其核心特性如下:

特性说明
Composition API引入 setup() 函数和响应式函数( ref , reactive ),逻辑复用更灵活,替代 Options API 的碎片化问题。
性能优化虚拟 DOM 重构( Diff 算法优化)、 Tree-shaking 支持(按需打包代码),包体积减少 41%。
TypeScript 支持源码用 TypeScript 重写,提供完整的类型定义,开发体验更友好。
新内置组件<Teleport> :跨组件层级渲染内容 - <Suspense> :异步组件加载状态管理
Fragment 支持单组件支持多根节点,无需外层包裹 <div>

2. 创建 Vue3 工程

  • vue-cli 创建

    npm create vue
  • vite 创建

    npm create vite@latest project-name -- --template vue
    cd <project-name>
    npm install
    // 启动开发服务器
    npm run dev

Vue3 项目结构说明:

my-vue-app/
├── src/
│   ├── assets/       # 静态资源(图片、CSS)
│   ├── components/   # 组件目录
│   ├── App.vue       # 根组件
│   └── main.js       # 应用入口文件
├── index.html        # 主 HTML 文件
├── vite.config.js    # Vite 配置文件
└── package.json

使用 vite 创建 vue3 项目和使用 vuecli 创建 vue3 项目的核心区别如下:

特性ViteVue CLI
底层构建工具基于 ESBuild (开发) + Rollup (生产)基于 Webpack
启动速度极快(毫秒级冷启动)较慢(需打包所有模块)
热更新(HMR)极快(按需更新)较慢(需重新打包部分模块)
配置复杂度轻量级,开箱即用复杂,依赖 vue.config.js
生态兼容性兼容 Rollup 插件生态依赖 Webpack 插件生态
浏览器支持默认面向现代浏览器(ES Modules)默认兼容旧浏览器(通过 polyfill)
官方定位下一代前端工具,Vue 3 官方推荐旧版 Vue 的官方脚手架

3. 常用的 Composition API

3.1 setup 函数

Vue 3 中, setup 函数是 Composition API 的核心,用于替代 Vue 2datamethods 等选项。 setup 函数在n beforeCreate 之前执行一次,所以 thisundefined ,所以在 setup 中不能使用 thissetup 函数的作用如下:

  • 初始化响应式数据:使用 refreactive 声明响应式变量。

  • 定义方法:直接在函数内声明并返回方法。

  • 访问组件上下文:通过参数获取 propsemitslots 等。

  • 组合逻辑:将相关逻辑组织在一起,提升代码可维护性。 setup 中无法访问 this ,所有逻辑通过 Composition API 实现。

    // 基本使用
    import { ref, reactive, onMounted } from 'vue';
    
    export default {
      props: {
        title: String
      },
      setup(props, context) {
        // 响应式数据
        const count = ref(0);
        const state = reactive({ name: 'Vue 3' });
    
        // 方法
        const increment = () => {
          count.value++;
        };
    
        // 生命周期钩子
        onMounted(() => {
          console.log('组件已挂载');
        });
    
        // 暴露给模板的数据和方法
        return {
          count,
          state,
          increment
        };
      }
    };

从上述案例可以看出 setup 函数有两个参数,如下:

  • prosprops 是组件接收的响应式属性,对应组件通过 props 选项声明的属性。

    • 响应式: props 是响应式对象,修改会导致组件更新。

    • 禁止直接解构赋值,否则会丢失响应式,需使用 toRefstoRef

      // 父组件通过<Demo msg='你好' school='CQUT'>向子组件传递数据
      
      import { toRefs, toRef } from 'vue'
      
      // 需要声明
      props = ['msg', 'school']
      setup(props) {
        // 解构所有 props 并保持响应性
        const { title } = toRefs(props)
        // 处理可选 prop
        const subtitle = toRef(props, 'subtitle')
        return { title, subtitle }
      }
    • 只读性:子组件不应直接修改 props ,需通过父组件传递新值。

  • context :提供组件上下文信息,包含三个核心属性: attrsslotsemit

    • attrs :包含未在 props 中声明的属性,如 classstylev-on 监听器等。避免基于 attrs 创建副作用,如 watch

    • slots :收到的插槽内容,相当于 this.$slots

      <Demo @hello="sayHello">
          // vue3中推荐使用v-slot
          <template v-slot:aaa>
      		<span>你好子组件</span>
          </template>
      </Demo>
    • emit :触发自定义事件,替代 Vue 2this.$emit

      // 父组件
      <Demo @hello="sayHello"></Demo>
      <script>
      	setup() {
          function sayHello() {
      
          }
      }
      
      <script>
      
      
      // 子组件
      emits = ['hello']
      export default {
        emits: ['submit'],
        setup(props, { emit }) {
          const handleClick = () => {
            emit('submit', '数据')
          }
          return { handleClick }
        }
      }

3.2 _ref 函数

Vue 3 中, ref 是组合式 API 的核心函数,用于创建响应式的引用对象。

<template>
  count: {{ count }} <button @click="increment">count++</button>
  str: {{ str }}
  person.name: {{ person.name }}
  <hr>
  person.person_info.number: {{ person.person_info.number}}
</template>

<script>
import {ref} from "vue";

export default {
    name: 'App',
    setup(props, context) {
      const count = ref(0);
      const str = ref('Hello Vue3');
      const person = ref({
        name: 'Tom',
        age: 18,
        person_info: {
          number: '666',
        }
      });

      function increment() {
        // 修改基本类型数据直接使用.value
        count.value++;
        // 对象数据类型只需要第一层.value
        person.value.person_info.number = '777';
      }

      return {
        count,
        str,
        increment,
        person
      };
    },
};
</script>

<style scoped>

</style>

对于 ref 函数,接收的数据可以是基本数据类型,也可以是对象数据类型,区别如下:

  • 基本数据类型:响应式依然是靠 Object.defineProperty()getset 完成的。
  • 对象数据类型:响应式本质上还是使用 Vue3 中的 reactive ,使用 Proxy 完成响应式。

3.3 reactive 函数

Vue 3 中, reactiveComposition API 的核心函数之一,用于创建响应式的对象或数组。 reactive 函数不能用于基本数据类型。从现象上来看, reactive 函数封装的对象数据操作不需要 .value

import { reactive } from 'vue';

const state = reactive({
  name: 'Alice',
  age: 30,
  address: {
    city: 'New York'
  }
});

// 修改属性(自动触发视图更新)
state.age = 31;
state.address.city = 'Los Angeles';


// 数组
const list = reactive(['apple', 'banana']);
list[0] = 'orange'; // 触发更新
list.push('grape');  // 触发更新
<template>
  <div>
    <p>Name: {{ state.name }}</p>
    <p>Age: {{ state.age }}</p>
    <p>City: {{ state.address.city }}</p>
  </div>
</template>

reactive 返回原始对象的 Proxy 代理,而非原始对象本身。验证如下:

console.log(state === reactive(state)); // true(同一代理)

ref 对比如下:

特性reactiveref
适用类型对象、数组基本类型、对象、数组
访问方式直接访问属性( state.name.value (脚本中)
解构赋值需使用 toRefs 保持响应性需保持 .value 引用
模板使用直接使用属性( state.name自动解包(无需 .value

3.4 computed 计算属性

通过 computed 函数创建计算属性,支持两种形式:

  • Getter 函数,只读

  • 对象形式,可读可写,含 getset 方法

    import { ref, computed } from 'vue'
    
    export default {
      setup() {
        const count = ref(0)
    
        // 1. 只读计算属性Getter
        const doubleCount = computed(() => count.value * 2)
    
        // 2. 可读写计算属性Getter + Setter
        const writableCount = computed({
          get: () => count.value + 1,
          set: (val) => {
            count.value = val - 1
          }
        })
    
        return { count, doubleCount, writableCount }
      }
    }
    <template>
      <div>
        <p>原始值: {{ count }}</p>
        <p>只读计算值: {{ doubleCount }}</p>
        <p>可写计算值: {{ writableCount }}</p>
        <button @click="writableCount = 10">修改可写计算属性</button>
      </div>
    </template>

computedref 的区别如下:

特性refcomputed
数据来源直接赋值依赖其他响应式数据计算得出
缓存有【依赖不变时复用缓存值】
可写性可直接修改 .value默认只读,需配置 set 方法

下面是一些计算属性的使用场景:

  • 依赖多个数据源

    const firstName = ref('John')
    const lastName = ref('Doe')
    
    const fullName = computed(() => `${firstName.value} ${lastName.value}`)
  • 结合 reactive 对象

    const state = reactive({
      price: 100,
      quantity: 2,
      total: computed(() => state.price * state.quantity)
    })
  • 链式计算属性

    const discount = ref(0.8)
    const discountedTotal = computed(() => state.total * discount.value)

对于 vue3 中的计算属性的使用,有以下注意点:

  • 不要在计算属性内执行异步操作或副作用 ,如修改 DOM 、发送请求。副作用应使用 watchwatchEffect 处理。

  • 确保计算函数 轻量 ,避免复杂计算。复杂逻辑可拆分为多个计算属性。如果计算属性依赖大量数据,考虑使用 computed 的缓存特性替代 method

  • 解构 reactive 对象中的计算属性会丢失响应性,需用 toRefs

    const state = reactive({
      count: 0,
      double: computed(() => state.count * 2)
    })
    
    // ❌ 错误:解构后失去响应性
    const { double } = state
    
    // ✅ 正确:保持响应性
    const { double } = toRefs(state)

3.5 watch 监视

Vue 3Composition API 中, watch 函数用于观察响应式数据的变化并执行副作用操作,如异步请求、 DOM 操作等。语法结构如下:

import { watch } from 'vue'

watch(
  source,              // 要监听的数据源 ref/reactive/getter/数组
  callback,            // 变化回调函数
  options?: {          // 可选的配置项
    immediate?: boolean,
    deep?: boolean,
    flush?: 'pre' | 'post' | 'sync'
  }
)
选项说明
immediate是否立即执行回调,默认 false
deep是否深度监听对象/数组,默认 false
flush回调触发时机: pre :组件更新前【默认】; post :组件更新后 ; sync :同步触发

watch 可以监听不同类型的数据源,如下:

  • 基本数据类型

    setup(props, context) {
          const count = ref(0);
          const str = ref('Hello Vue3');
    
          function increment() {
            count.value++;
          }
    
          function changePName() {
            person.name = 'Jerry';
          }
    
          // 监视单个基本数据类型
          watch(count, () => {
            console.log('count changed');
          })
    
          // 监视多个基本数据类型
          watch([count, str], () => {
            console.log('count 或 str changed');
          })
    }
  • 对象数据类型

    • 监听整个 reactive 对象,默认浅层监听。

      import { reactive, watch } from 'vue'
      
      const state = reactive({ 
        name: 'Alice', 
        profile: { age: 25 }
      })
      
      // 浅层监听,只响应对象引用变化
      watch(
        state,
        (newVal, oldVal) => {
          console.log('state 变化:', newVal)
        }
      )
      
      // ❌ 不会触发, 修改嵌套属性,对象引用未变
      state.profile.age = 30 
      
      // ✅ 触发(替换整个对象)
      state = reactive({ name: 'Bob' }) 
      
    • 深度监听嵌套属性,即 deep: true 。在该情况需要注意 newValoldVal 会指同一引用。

      watch(
        state,
        (newVal) => {
          console.log('深度监听:', newVal)
        },
        { deep: true } // 启用深度监听
      )
      
      // ✅ 触发 修改嵌套属性
      state.profile.age = 30 
      
    • 监听特定属性

      • 使用 getter 函数监听单个属性

        watch(
          () => state.name,
          (newName, oldName) => {
            console.log('name变化:', oldName, '→', newName)
          }
        )
        
        // ✅ 触发
        state.name = 'Bob' 
        
      • 监听嵌套对象属性

        watch(
          () => state.profile.age,
          (newAge) => {
            console.log('age变化:', newAge)
          }
        )
        
        // ✅ 触发
        state.profile.age = 30 
        
      • 监听多个属性,使用数组形式

        watch(
          [() => state.name, () => state.profile.age],
          ([newName, newAge], [oldName, oldAge]) => {
            console.log(`多个变化: name=${oldName}${newName}, age=${oldAge}${newAge}`)
          }
        )

3.6 watchEffect 函数

Vue 3Composition API 中, watchEffect 是一个用于 自动追踪依赖 并执行副作用的函数。它的特点是无需显式声明依赖,而是通过回调函数内部实际使用的响应式数据自动收集依赖。语法结构如下:

import { watchEffect } from 'vue'

const stop = watchEffect(
  (onInvalidate) => {
    // 副作用逻辑(自动追踪依赖)
    // onInvalidate: 注册清理函数(可选)
  },
  {
    flush?: 'pre' | 'post' | 'sync', // 回调执行时机
    onTrack?: (event) => void,        // 调试依赖追踪
    onTrigger?: (event) => void       // 调试依赖触发
  }
)

// 手动停止监听
stop()

其核心特性如下:

  • 自动依赖收集: watchEffect 会自动追踪回调函数内部使用到的响应式数据。当依赖的数据变化时,回调函数会重新执行。
  • 立即执行:默认在初始化时立即执行一次回调,类似 watchimmediate: true
  • 无新旧值:回调函数不提供新旧值参数,仅关注当前值。
特性watchEffectwatch
依赖声明自动收集回调内的依赖显式声明数据源
初始执行立即执行需配置 immediate: true
新旧值提供 newValoldVal
适用场景依赖复杂或需要立即执行的副作用需要精确控制监听源和访问旧值时

3.7 Vue3 生命周期

首先是 Option API 钩子,如下:

钩子函数执行阶段
beforeCreate组件实例初始化前【数据观测和事件配置未完成】
created组件实例初始化完成【数据观测完成,但 DOM 未生成】
beforeMount组件挂载到 DOM
mounted组件挂载到 DOM 后【可访问 DOM 元素】
beforeUpdate响应式数据变化, DOM 重新渲染前
updated响应式数据变化, DOM 重新渲染后
beforeUnmount组件卸载前【替代 Vue 2 的 beforeDestroy
unmounted组件卸载后【替代 Vue 2 的 destroyed
errorCaptured捕获子孙组件错误时触发
renderTracked渲染函数依赖的响应式数据被追踪时触发【开发模式调试用】
renderTriggered渲染函数依赖的响应式数据触发重新渲染时【开发模式调试用】

上述钩子使用配置项,符合 Vue2 风格,在 Vue3 中,推荐使用 Composition API ,如下::

钩子函数对应 Options API 钩子
onBeforeMountbeforeMount
onMountedmounted
onBeforeUpdatebeforeUpdate
onUpdatedupdated
onBeforeUnmountbeforeUnmount
onUnmountedunmounted
onErrorCapturederrorCaptured
onRenderTrackedrenderTracked
onRenderTriggeredrenderTriggered

生命周期执行流程图如下:同时使用 Option API 钩子和 Composition API 优先使用 Composition API

父组件 setup → 父组件 beforeCreate → 父组件 created → 父组件 beforeMount
子组件 setup → 子组件 beforeCreate → 子组件 created → 子组件 beforeMount → 子组件 mounted
父组件 mounted → 父组件 beforeUpdate → 子组件 beforeUpdate → 子组件 updated → 父组件 updated
父组件 beforeUnmount → 子组件 beforeUnmount → 子组件 unmounted → 父组件 unmounted

setup() 函数中,通过 onXxx 函数注册生命周期钩子,如下:

import { onMounted, onUpdated } from 'vue'

export default {
  setup() {
    // 挂载后操作
    onMounted(() => {
      // 异步请求推荐位置
      const response = await fetch('https://api.example.com/data')
      data.value = await response.json()
        
      timer.value = setInterval(() => {
      	console.log('定时器运行中...')
    	}, 1000)
      })

    // 更新后操作
    onUpdated(() => {
      clearInterval(timer.value)
    })
  }
}

3.8 自定义 hook

组合式函数是一个普通 JavaScript 函数,内部使用 VueComposition API ,如 ref , reactivewatch 等的封装逻辑。命名通常以 use 开头,如 useFetch , useMouse ,以便代码可读性。与 Mixins 对比如下:

特性组合式函数Mixins
逻辑复用方式函数调用,显式暴露状态和方法隐式合并到组件选项对象中
命名冲突无【通过解构命名控制】容易发生【需手动处理】
类型支持天然支持 TypeScript类型推断困难
代码组织按功能组织,代码集中分散在多个选项中

从上述介绍不难看出,自定义 hook 就是将 setup 中的 Composition API 进行了封装,以提高代码复用率。通常建立一个 hooks 文件夹,在里面存放自己的 hook 。如下面封装一个获取鼠标位置的 hook

// hooks/useMouse.js
import { ref, onMounted, onUnmounted } from 'vue'

export function useMouse() {
  const x = ref(0)
  const y = ref(0)

  const update = (event) => {
    x.value = event.pageX
    y.value = event.pageY
  }

  onMounted(() => window.addEventListener('mousemove', update))
  onUnmounted(() => window.removeEventListener('mousemove', update))

  return { x, y }
}
// 在组件中使用
<script setup>
import { useMouse } from './useMouse'

// 调用组合式函数,解构响应式状态
const { x, y } = useMouse()
</script>

<template>
  <div>鼠标位置{{ x }}, {{ y }}</div>
</template>

3.9 toReftoRefs

Vue 3 中, toReftoRefs 是用于处理响应式对象属性的工具函数,它们的主要作用是将响应式对象的属性转换为 ref 对象,以保持响应性。它们的核心区别如下:

特性toReftoRefs
作用对象响应式对象的 单个属性响应式对象的 所有属性
返回值单个 ref 对象包含所有属性的 ref 对象的普通对象
适用场景需要保留对某个属性的响应式引用结构整个响应式对象,保持所有属性的响应性
是否处理不存在属性可为不存在的属性创建 ref 【需谨慎】仅处理对象已存在的属性
  • toRef 的使用:将响应式对象的某个属性转换为 ref

    import { reactive, toRef } from 'vue'
    
    const state = reactive({ name: 'Alice', age: 30 })
    const nameRef = toRef(state, 'name')
    
    // 修改 ref 会同步到原对象
    nameRef.value = 'Bob'
    console.log(state.name) // 输出: 'Bob'
    
    // 处理不存在的属性:若属性不存在,toRef 会创建一个可写的 ref,但不会自动添加到原对象
    
    const state = reactive({ name: 'Alice' })
    const ageRef = toRef(state, 'age') // 原对象无 age 属性
    
    // 修改 ref 的 value
    ageRef.value = 30
    console.log(state.age) // 输出: undefined 原对象未添加该属性
    
  • toRefs 的使用:将响应式对象的所有属性转换为 ref

    import { reactive, toRefs } from 'vue'
    
    const state = reactive({ name: 'Alice', age: 30 })
    const stateRefs = toRefs(state)
    
    // 结构后仍保持响应性
    const { name, age } = stateRefs
    name.value = 'Bob'
    console.log(state.name) // 输出: 'Bob'
    
    // 处理嵌套对象
    const state = reactive({ info: { age: 30 } })
    const stateRefs = toRefs(state)
    
    // stateRefs.info 是 ref,其 value 是响应式对象
    stateRefs.info.value.age = 31 // 响应式更新
    

那么什么时候使用 toRef ,什么时候使用 toRefs 呢?

  • 单属性引用 → toRef
  • 多属性结构 → toRefs

4. 其它 Composition API

4.1 shallowReactiveshallowRef

Vue 3 中, shallowReactiveshallowRef 是用于浅层响应式处理的 API,适用于需要优化性能的场景。它们与标准 reactiveref 的关键区别在于仅对顶层数据做响应式处理,不递归追踪深层对象的变化。

  • shallowReactive :只对对象的第一层属性进行响应式处理,嵌套对象保持原始类型。当使用数据量大的对象,且仅需监听顶层字段变化的场景如配置对象、表单控件集合时使用。若需要响应嵌套对象的变化,需手动替换整个嵌套对象。

    import { shallowReactive, watchEffect } from 'vue'
    
    const state = shallowReactive({
      name: 'Alice',      // 响应式(顶层属性)
      profile: {          // ❌ 非响应式(嵌套对象)
        age: 25
      }
    })
    
    // 监听顶层属性变化
    watchEffect(() => {
      console.log('名字变化:', state.name) // ✅ 触发
    })
    
    // 监听嵌套属性变化
    watchEffect(() => {
      console.log('年龄变化:', state.profile.age) // ❌ 不触发
    })
    
    // 修改顶层属性 → 触发响应
    state.name = 'Bob'
    
    // 修改嵌套属性 → 不触发响应
    state.profile.age = 30
  • shallowRef :只处理基本数据类型的响应式,不进行对象的响应式处理。适合场景为存储大型对象,如 DOM 元素、复杂数据结构,避免深度响应式开销 || 需要整体替换值的场景,如分页数据更新。

    import { shallowRef, watchEffect } from 'vue'
    
    // 存储一个对象
    const userRef = shallowRef({
      name: 'Alice',
      profile: { age: 25 }
    })
    
    // 监听整个 .value 变化
    watchEffect(() => {
      console.log('用户变化:', userRef.value) // ✅ 触发(当 userRef.value 被替换时)
    })
    
    // 监听嵌套属性变化
    watchEffect(() => {
      console.log('年龄变化:', userRef.value.profile.age) // ❌ 不触发
    })
    
    // 修改嵌套属性 → 不触发响应
    userRef.value.profile.age = 30
    
    // 替换整个值 → 触发响应
    userRef.value = {
      name: 'Bob',
      profile: { age: 30 }
    }

4.2 readonlyshallowReadonly

Vue 3 中, readonlyshallowReadonly 是用于创建 只读响应式对象 的工具,适用于需要保护数据不被意外修改的场景,如传递 props 、状态管理等。以下是它们的详细用法和区别:

  • readonly :递归地将整个对象及其嵌套属性变为只读。依然是响应式对象,但不可修改。

    import { reactive, readonly, watchEffect } from 'vue'
    
    const original = reactive({ 
      name: 'Alice', 
      profile: { age: 25 }
    })
    
    const readOnlyObj = readonly(original)
    
    // 尝试修改会触发警告(开发环境下)且操作无效
    readOnlyObj.name = 'Bob'          // ❌ 失败
    readOnlyObj.profile.age = 30      // ❌ 失败
    
    // 监听只读对象变化(仍会响应原始对象的修改)
    watchEffect(() => {
      console.log('只读对象的值:', readOnlyObj.name) // 原始对象修改时触发
    })
    
    // 修改原始对象 → 只读对象同步更新
    original.name = 'Bob'             // ✅ watchEffect 会触发
    
  • shallowReadonly :仅将对象的 顶层属性 变为只读,嵌套对象仍可变。顶层属性不可修改,但嵌套对象保持原始响应性。

    import { reactive, shallowReadonly } from 'vue'
    
    const original = reactive({ 
      name: 'Alice', 
      profile: { age: 25 }
    })
    
    const shallowReadOnlyObj = shallowReadonly(original)
    
    // 修改顶层属性 → 失败
    shallowReadOnlyObj.name = 'Bob'   // ❌ 失败
    
    // 修改嵌套属性 → 成功
    shallowReadOnlyObj.profile.age = 30 // ✅ 成功
    

4.3 toRawmarkRaw

Vue 3 中, toRawmarkRaw 是用于处理响应式对象和原始对象的工具函数,适用于需要绕过响应式系统或优化性能的场景。以下是它们的详细用法和核心区别:

  • toRaw :返回由 reactivereadonlyAPI 创建的响应式对象的原始非响应式对象。用于直接操作原始对象,避免响应式追踪的开销,如一次性批量修改数据。

    import { reactive, toRaw } from 'vue'
    
    const reactiveObj = reactive({ name: 'Alice', profile: { age: 25 } })
    const rawObj = toRaw(reactiveObj)
    
    // 修改原始对象 → 不会触发响应式更新
    rawObj.name = 'Bob'
    console.log(reactiveObj.name) // 输出: 'Bob'(数据已改,但不会触发视图更新)
    
    // 响应式对象仍可触发更新
    reactiveObj.name = 'Charlie' // ✅ 触发响应式更新
    
    const rawData = toRaw(reactiveData)
    rawData.items.push(...newItems) // 避免多次触发更新
    // 手动触发更新(如强制重新渲染)
    triggerRef(reactiveData)
  • makeRaw :标记一个对象,使其永远不会被转换为响应式对象。避免 Vue 对大型对象或第三方实例进行不必要的响应式处理。

    import { reactive, markRaw } from 'vue'
    
    // 标记对象为原始
    const staticData = markRaw({ 
      config: { theme: 'dark' }, 
      methods: { /* 复杂方法 */ }
    })
    
    // 即使被包裹在响应式对象中,staticData 仍保持原始
    const state = reactive({
      staticData, // ❌ 不会被代理
      dynamicData: { count: 0 } // ✅ 会被代理
    })
    
    // 修改 staticData 的嵌套属性 → 不会触发响应式
    state.staticData.config.theme = 'light'
    console.log(state.staticData.config.theme) // 'light'(数据已改,但无响应式)
    
特性toRawmarkRaw
作用对象响应式对象,如 reactiveref任何对象
返回值原始对象被标记的原始对象
修改原始对象的影响响应式对象数据同步更新,但 不触发响应式追踪对象永远不会被 Vue 代理
适用场景临时操作原始数据永久禁止对象被响应式处理

4.4 customRef

Vue 3 中, customRef 允许开发者创建自定义的响应式引用 ref ,通过手动控制依赖追踪 track 和更新触发 trigger ,适用于需要精细控制响应式行为的场景,如防抖、异步操作等。 customRef 接收一个工厂函数,该函数需返回包含 getset 方法的对象:

  • get() :在访问值时调用,需手动调用 track() 追踪依赖。

  • set(newValue) :在修改值时调用,需手动调用 trigger() 触发更新。

    <template>
      <input v-model="text" />
      <div>Debounced Value: {{ text }}</div>
    </template>
    
    <script setup>
    import { customRef } from 'vue'
    
    function debouncedRef(value, delay = 200) {
      let timeout
      return customRef((track, trigger) => ({
        get() {
          track()
          return value
        },
        set(newValue) {
          clearTimeout(timeout)
          timeout = setTimeout(() => {
            value = newValue
            trigger()
          }, delay)
        }
      }))
    }
    
    const text = debouncedRef('', 500)
    </script>

在实际开发中,有以下使用场景:

  • 防抖 Ref :创建一个防抖 Ref,延迟触发更新。

    function debouncedRef(initialValue, delay = 200) {
      let timeout
      return customRef((track, trigger) => {
        return {
          get() {
            track()
            return initialValue
          },
          set(newValue) {
            clearTimeout(timeout)
            timeout = setTimeout(() => {
              initialValue = newValue
              trigger() // 延迟触发更新
            }, delay)
          }
        }
      })
    }
    
    // 使用
    const text = debouncedRef('', 500)
  • 异步数据 Ref :在设置值时执行异步操作,完成后触发更新。

    function asyncRef(initialValue) {
      return customRef((track, trigger) => {
        let value = initialValue
        return {
          get() {
            track()
            return value
          },
          async set(newValue) {
            try {
              const result = await fetchData(newValue) // 假设异步请求
              value = result
              trigger()
            } catch (error) {
              console.error('请求失败:', error)
            }
          }
        }
      })
    }
    
    // 使用
    const data = asyncRef(null)

customRef 适用于需要手动控制响应式行为的场景,如防抖、异步更新、集成第三方库等。

4.5 provideinject

Vue 3 中, provideinject 用于实现跨组件层级的数据传递,尤其适合祖先组件与深层嵌套的后代组件之间的通信。以下是详细的使用指南和最佳实践:

  • provide :父组件提供数据,在祖先组件的 setup 函数中,使用 provide 提供数据。

    // ParentComponent.vue
    import { provide, ref } from 'vue'
    
    export default {
      setup() {
        const message = ref('Hello from parent')
        provide('message', message) // 提供响应式数据
        return { message }
      }
    }
  • inject :子组件注入数据,在后代组件的 setup 函数中,使用 inject 获取数据。

    // ChildComponent.vue
    import { inject } from 'vue'
    
    export default {
      setup() {
        const message = inject('message', '默认值') // 注入数据,可设置默认值
        return { message }
      }
    }

当然,也可以实现响应式数据传递,如下::

  • 提供响应式数据:确保提供的数据是响应式【 refreactive 】,以便子组件能检测到变化。

    // ParentComponent.vue
    const count = ref(0)
    provide('count', count)
  • 在子组件中修改数据:若需子组件修改数据,可在父组件中提供方法。

    // ParentComponent.vue
    const increment = () => count.value++
    provide('count', { count, increment })
    // ChildComponent.vue
    const { count, increment } = inject('count')

对于上述两个 API 的使用,需要注意以下问题:

  • 响应式数据必须显式提供:若提供非响应式数据,子组件无法检测变化。

    provide('staticData', { key: 'value' }) // ❌ 非响应式
    provide('dynamicData', ref({ key: 'value' })) // ✅ 响应式
    
  • 单向数据流:避免子组件直接修改注入的响应式数据,除非父组件明确允许。

    // ParentComponent.vue
    provide('data', readonly(data)) // 使用 readonly 保护数据
    

4.6 响应式数据的判断

  • isRef :检查一个值是否为一个 Ref 对象。
  • isReactive :检查一个值是否由 reactive 创建的响应式代理。
  • isReadonly :检查一个对象是否由 readonly 创建的只读代理。
  • isProxy :检查一个对象是否由 reactivereadonly 方法创建的代理。

5. Fragment 组件

Vue 3 中, Fragment 片段是一项重要的新特性,允许组件模板包含多个根节点而无需包裹在一个父容器中。

<!-- 合法模板Vue 3 -->
<template>
  <h1>标题</h1>
  <p>内容</p>
  <button>按钮</button>
</template>

6. Teleport 组件

Vue 3 中, <Teleport> 组件用于将模板内容渲染到 DOM 中的指定位置,常用于解决组件层级导致的样式或布局问题,如模态框、全局通知等。基础用法用法如下:

  • 定义目标容器:在 HTML 中添加目标容器,通常位于根节点附近。

    <!-- public/index.html -->
    <body>
      <div id="app"></div>
      <div id="modal-container"></div> <!-- Teleport 目标容器 -->
    </body>
  • 在组件中使用 <Teleport>

    <!-- MyComponent.vue -->
    <template>
      <button @click="showModal = true">打开模态框</button>
    
      <Teleport to="#modal-container">
        <div v-if="showModal" class="modal">
          <h2>标题</h2>
          <p>内容...</p>
          <button @click="showModal = false">关闭</button>
        </div>
      </Teleport>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    const showModal = ref(false)
    </script>

动态目标与禁用撞状态:

  • 动态切换目标容器

    <Teleport :to="isMobile ? '#mobile-modal' : '#desktop-modal'">
      <!-- 内容 -->
    </Teleport>
  • 禁用 Teleport :通过 :disabled 控制内容是否保留在原位置。

    <Teleport to="#modal-container" :disabled="forceInline">
      <!--  forceInline  true 内容不传送 -->
    </Teleport>

Transition 组件结合使用:

<Teleport to="#modal-container">
  <Transition name="fade">
    <div v-if="showModal" class="modal">
      <!-- 内容 -->
    </div>
  </Transition>
</Teleport>

<style>
.fade-enter-active, .fade-leave-active {
  transition: opacity 0.3s;
}
.fade-enter-from, .fade-leave-to {
  opacity: 0;
}
</style>

多内容传送与顺序:多个 Teleport 内容按代码顺序渲染到目标容器。

<Teleport to="#notifications">
  <div class="notification">通知 1</div>
</Teleport>

<Teleport to="#notifications">
  <div class="notification">通知 2</div>
</Teleport>

<!-- 目标容器显示顺序通知 1  通知 2 -->

7. Suspense 组件

Vue 3 中, <Suspense> 组件用于优雅地处理异步组件或异步数据的加载状态,允许开发者定义加载中和加载完成后的展示内容。以下是其详细使用指南和常见场景示例:

  • 基础用法:当加载异步组件时,展示加载状态。

    <template>
      <Suspense>
        <!-- 默认插槽异步组件 -->
        <template #default>
          <AsyncComponent />
        </template>
    
        <!-- 加载中状态 -->
        <template #fallback>
          <div>加载中...</div>
        </template>
      </Suspense>
    </template>
    
    <script setup>
    import { defineAsyncComponent } from 'vue'
    
    // 定义异步组件(返回 Promise)
    const AsyncComponent = defineAsyncComponent(() =>
      import('./AsyncComponent.vue')
    )
    </script>
  • 处理异步数据:在组件 setup 中使用 async 函数处理异步数据。

    <template>
      <Suspense>
        <template #default>
          <UserProfile :user="userData" />
        </template>
        <template #fallback>
          <div>加载用户数据...</div>
        </template>
      </Suspense>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    
    const userData = ref(null)
    
    // 异步获取数据(setup 函数支持 async)
    async function fetchUser() {
      const response = await fetch('/api/user')
      userData.value = await response.json()
    }
    
    await fetchUser() // 等待数据加载完成
    </script>
  • 错误处理

    • 捕获异步错误:使用 onErrorCaptured 钩子捕获错误并显示友好提示。

      <template>
        <div v-if="error">{{ error.message }}</div>
        <Suspense v-else>
          <template #default>
            <AsyncComponent />
          </template>
          <template #fallback>
            <div>加载中...</div>
          </template>
        </Suspense>
      </template>
      
      <script setup>
      import { ref, onErrorCaptured } from 'vue'
      
      const error = ref(null)
      
      onErrorCaptured((err) => {
        error.value = err
        return false // 阻止错误继续向上传播
      })
      </script>
    • 重试机制:允许用户点击重试加载。

      <template>
        <div v-if="error">
          <p>加载失败{{ error.message }}</p>
          <button @click="retry">重试</button>
        </div>
        <Suspense v-else>
          <!-- ... -->
        </Suspense>
      </template>
      
      <script setup>
      const error = ref(null)
      const retry = () => {
        error.value = null
        // 重新执行异步操作(如重新加载组件或数据)
      }
      </script>
    • 动态切换异步组件:结合动态组件 <component :is="..."> 实现按需加载。

      <template>
        <button @click="toggleComponent">切换组件</button>
        <Suspense>
          <template #default>
            <component :is="currentComponent" />
          </template>
          <template #fallback>
            <div>加载组件中...</div>
          </template>
        </Suspense>
      </template>
      
      <script setup>
      import { ref, defineAsyncComponent } from 'vue'
      
      const components = {
        A: defineAsyncComponent(() => import('./ComponentA.vue')),
        B: defineAsyncComponent(() => import('./ComponentB.vue'))
      }
      
      const currentComponent = ref('A')
      const toggleComponent = () => {
        currentComponent.value = currentComponent.value === 'A' ? 'B' : 'A'
      }
      </script>

8. Vue3 中的其它变化

Vue3 中,因为 Vue 不能使用,将原来的全局 API 转移到了应用实例 app 上,如下:

2.X全局API3.X全局API
Vue.config.xxxapp.config.xxx
Vue.config.productionTip移除
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties

除了上述全局 API 的转移,还有以下改变:

  • data 选项始终被声明为一个函数。
  • 过度类名的更改
    Vue 2 类名Vue 3 类名说明
    .v-enter.v-enter-from进入动画的初始状态
    .v-leave.v-leave-from离开动画的初始状态
    .v-enter-to无变化进入动画的结束状态
    .v-leave-to无变化离开动画的结束状态
    .v-enter-active无变化进入动画的激活状态(过渡属性)
    .v-leave-active无变化离开动画的激活状态
  • 移除 keyCode 作为 v-on 的修饰符,同时也不再支持 config.keyCodes
  • 移除 v-on.native 修饰符。
  • 移除过滤器 filter
  • ......