这份手册涵盖 Vue3 从入门到进阶的核心知识点,兼顾新手友好性和实战实用性,以 组合式 API(Composition API) 为核心(Vue3 推荐用法),配套完整示例和关键说明。

一、环境搭建

1. 前置条件

安装 Node.js(推荐 v16.x 及以上版本),自带 npm/yarn 包管理器:

2. 两种项目创建方式

方式 1:Vue CLI(传统脚手架,适合新手)

  1. 全局安装 Vue CLI
    bash
     
    运行
     
     
     
     
    npm install -g @vue/cli
    # 或 yarn global add @vue/cli
    
     
     
  2. 创建项目
    bash
     
    运行
     
     
     
     
    vue create vue3-demo
    
     
     
  3. 选择配置:
    • 上下箭头选择 Default (Vue 3)(默认 Vue3 配置)或 Manually select features(自定义配置)
    • 回车等待项目创建完成
  4. 启动项目
    bash
     
    运行
     
     
     
     
    cd vue3-demo
    npm run serve
    # 或 yarn serve
    
     
     

方式 2:Vite(新一代构建工具,更快更轻量,推荐生产使用)

  1. 创建 Vite + Vue3 项目
    bash
     
    运行
     
     
     
     
    # npm 6.x
    npm create vite@latest vue3-vite-demo --template vue
    # npm 7+(需额外加 --)
    npm create vite@latest vue3-vite-demo -- --template vue
    # yarn
    yarn create vite vue3-vite-demo --template vue
    
     
     
  2. 安装依赖并启动
    bash
     
    运行
     
     
     
     
    cd vue3-vite-demo
    npm install
    # 或 yarn install
    npm run dev
    # 或 yarn dev
    
     
     

3. 项目核心结构(Vite/Vue CLI 通用核心目录)

plaintext
 
 
 
 
 
vue3-demo/
├── node_modules/       # 项目依赖包
├── public/             # 静态资源(不会被打包处理)
├── src/                # 核心源码目录(重点关注)
│   ├── assets/         # 静态资源(会被打包处理,如图片、样式)
│   ├── components/     # 公共组件
│   ├── App.vue         # 根组件
│   ├── main.js         # 入口文件
│   └── style.css       # 全局样式
├── package.json        # 项目配置(依赖、脚本命令)
└── vite.config.js      # Vite 配置文件(Vue CLI 对应 vue.config.js)
 

二、Vue3 核心基础

1. 单文件组件(SFC:.vue 文件)

Vue3 单文件组件由 3 个部分组成,是 Vue 项目的基础单位:
vue
 
 
 
 
 
<!-- src/components/HelloWorld.vue -->
<template>
  <!-- 模板区域:HTML 结构,支持 Vue 模板语法 -->
  <div class="hello">
    <h1>{{ msg }}</h1>
    <button @click="changeMsg">修改消息</button>
  </div>
</template>

<script setup>
// 脚本区域:Vue3 推荐使用 <script setup> 语法糖(简化组合式 API 写法)
// 无需手动导出,变量/方法直接在模板中使用

// 1. 定义响应式变量(ref 用于基本类型)
import { ref } from 'vue'
const msg = ref('Hello Vue3!')

// 2. 定义方法
const changeMsg = () => {
  msg.value = 'Vue3 组合式 API 真好用!'
  // 注意:ref 定义的变量,在脚本中需通过 .value 访问/修改,模板中无需 .value
}
</script>

<style scoped>
/* 样式区域:scoped 表示样式仅作用于当前组件,避免样式污染 */
.hello {
  text-align: center;
  margin-top: 50px;
}
button {
  padding: 8px 16px;
  cursor: pointer;
}
</style>
 

2. 组合式 API 核心:响应式数据

Vue3 提供两种核心响应式 API:ref 和 reactive,用于创建响应式数据(数据变化时,视图自动更新)。

(1)ref:用于基本类型 + 引用类型

(2)reactive:用于引用类型

(3)ref vs reactive 核心区别

特性 ref reactive
支持数据类型 基本类型 + 引用类型 仅支持引用类型
脚本中访问方式 需通过 .value 直接访问属性,无需 .value
解构赋值兼容性 解构后仍保持响应式(需 toRefs) 直接解构会丢失响应式
使用便捷性 简单易用,推荐优先使用 适合复杂对象 / 状态聚合

(4)辅助响应式 API

3. 计算属性与侦听器

(1)computed:计算属性

(2)watch:侦听器

(3)watchEffect:自动收集依赖的侦听器

4. 模板语法

(1)插值表达式

(2)指令

指令是 Vue 提供的特殊 HTML 特性,以 v- 开头,用于操作 DOM。
  1. v-bind:绑定 HTML 属性(缩写::)
    vue
     
     
     
     
     
    <template>
      <!-- 绑定单个属性 -->
      <div :id="boxId" :class="boxClass">绑定样式类</div>
      <!-- 绑定对象形式的 class -->
      <div :class="{ active: isActive, 'text-red': isRed }">动态class</div>
      <!-- 绑定对象形式的 style -->
      <div :style="{ color: textColor, fontSize: '16px' }">动态style</div>
      <!-- 绑定组件 props(后续组件通信会详细讲) -->
      <Child :msg="parentMsg" />
    </template>
    
     
     
  2. v-on:绑定事件(缩写:@)
    vue
     
     
     
     
     
    <template>
      <!-- 绑定基本事件 -->
      <button @click="handleClick">点击事件</button>
      <!-- 传递参数 -->
      <button @click="handleParams(123, $event)">传递参数($event 是原生事件对象)</button>
      <!-- 事件修饰符:.stop(阻止冒泡)、.prevent(阻止默认行为)等 -->
      <a @click.prevent="handleLink" href="https://vuejs.org">阻止默认跳转</a>
      <!-- 按键修饰符:.enter(回车触发)等 -->
      <input @keyup.enter="handleEnter" placeholder="回车触发" />
    </template>
    
     
     
  3. v-if / v-else-if / v-else:条件渲染(动态创建 / 销毁 DOM)
    vue
     
     
     
     
     
    <template>
      <div v-if="score >= 90">优秀</div>
      <div v-else-if="score >= 80">良好</div>
      <div v-else-if="score >= 60">及格</div>
      <div v-else>不及格</div>
    </template>
    
     
     
  4. v-show:条件显示(切换 DOM 的 display 属性,不销毁 DOM)
    vue
     
     
     
     
     
    <template>
      <div v-show="isShow">v-show 显示/隐藏</div>
    </template>
    
     
     
    • v-if vs v-show:频繁切换用 v-show(性能更好),首次渲染或切换不频繁用 v-if(节省 DOM 资源)
  5. v-for:列表渲染
    vue
     
     
     
     
     
    <template>
      <!-- 遍历数组:item 是当前项,index 是索引(可选) -->
      <ul>
        <li v-for="(item, index) in todoList" :key="item.id">
          {{ index + 1 }}. {{ item.title }}
        </li>
      </ul>
      <!-- 遍历对象:value 是值,key 是键,index 是索引(可选) -->
      <div v-for="(value, key, index) in user" :key="key">
        {{ index }}. {{ key }}: {{ value }}
      </div>
    </template>
    
     
     
    • 必传 :key:用于 Vue 识别列表项的唯一性,优化列表渲染性能(推荐使用唯一 ID,不推荐使用 index)
  6. v-model:双向数据绑定(简化表单交互)
    vue
     
     
     
     
     
    <template>
      <!-- 输入框 -->
      <input v-model="username" placeholder="请输入用户名" />
      <!-- 复选框 -->
      <input type="checkbox" v-model="isAgree" /> 同意协议
      <!-- 单选框 -->
      <input type="radio" v-model="gender" value="male" /> 男
      <input type="radio" v-model="gender" value="female" /> 女
      <!-- 下拉框 -->
      <select v-model="city">
        <option value="">请选择城市</option>
        <option value="beijing">北京</option>
        <option value="shanghai">上海</option>
      </select>
    </template>
    
    <script setup>
    import { ref } from 'vue'
    const username = ref('')
    const isAgree = ref(false)
    const gender = ref('male')
    const city = ref('')
    </script>
    
     
     

三、组件系统

1. 组件的定义与注册

Vue3 组件分为 局部注册(推荐,仅当前组件可用)和 全局注册(全局可用,适合通用组件)。

(1)局部注册

直接在使用组件的父组件中导入并使用(无需额外注册,<script setup> 中自动局部注册)
vue
 
 
 
 
 
<!-- 父组件:Parent.vue -->
<template>
  <div>
    <h2>父组件</h2>
    <!-- 直接使用子组件 -->
    <Child :msg="parentMsg" />
  </div>
</template>

<script setup>
// 导入子组件(自动局部注册)
import Child from './Child.vue'
import { ref } from 'vue'
const parentMsg = ref('来自父组件的消息')
</script>

<!-- 子组件:Child.vue -->
<template>
  <div class="child">
    <h3>子组件</h3>
    <div>{{ msg }}</div>
  </div>
</template>

<script setup>
// 定义 props(接收父组件传递的数据)
const props = defineProps({
  msg: {
    type: String,
    required: true, // 是否必传
    default: '' // 默认值(非必传时生效)
  }
})
</script>
 

(2)全局注册

在入口文件 main.js 中注册,全局所有组件均可直接使用
javascript
 
运行
 
 
 
 
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import GlobalChild from './components/GlobalChild.vue'

const app = createApp(App)

// 全局注册组件
app.component('GlobalChild', GlobalChild)

app.mount('#app')
 
vue
 
 
 
 
 
<!-- 任意组件中直接使用 -->
<template>
  <GlobalChild />
</template>
 

2. 组件通信

Vue3 组件通信的核心方式如下(按使用频率排序):

(1)Props:父组件 → 子组件

(2)Emits:子组件 → 父组件

vue
 
 
 
 
 
<!-- 子组件:Child.vue -->
<template>
  <button @click="handleSend">向父组件传递数据</button>
</template>

<script setup>
// 定义要触发的事件
const emit = defineEmits(['childMsg'])

const handleSend = () => {
  // 触发事件并传递数据
  emit('childMsg', '来自子组件的消息', 123)
}
</script>

<!-- 父组件:Parent.vue -->
<template>
  <Child @childMsg="handleChildMsg" />
  <div>子组件传递的数据:{{ childData }}</div>
</template>

<script setup>
import Child from './Child.vue'
import { ref } from 'vue'
const childData = ref('')

// 接收子组件数据
const handleChildMsg = (msg, num) => {
  console.log(num) // 123
  childData.value = msg
}
</script>
 

(3)provide /inject:跨层级组件通信(父 → 孙 / 深层子组件)

vue
 
 
 
 
 
<!-- 父组件:Parent.vue -->
<template>
  <Child />
</template>

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

// 提供普通数据(非响应式)
provide('staticMsg', '父组件提供的静态消息')

// 提供响应式数据
const parentCount = ref(0)
provide('reactiveCount', parentCount)

// 提供方法(用于子组件修改父组件数据)
provide('updateCount', () => {
  parentCount.value++
})
</script>

<!-- 深层子组件:GrandChild.vue -->
<template>
  <div>静态消息:{{ staticMsg }}</div>
  <div>响应式计数:{{ reactiveCount }}</div>
  <button @click="updateCount">修改父组件计数</button>
</template>

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

// 注入数据
const staticMsg = inject('staticMsg')
const reactiveCount = inject('reactiveCount')
const updateCount = inject('updateCount')
</script>
 

(4)ref 引用:父组件获取子组件实例 / DOM 元素

vue
 
 
 
 
 
<!-- 子组件:Child.vue -->
<template>
  <div>子组件内部计数:{{ childCount }}</div>
</template>

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

const childCount = ref(0)
const childMethod = () => {
  childCount.value++
}

// 暴露内部属性/方法给父组件
defineExpose({
  childCount,
  childMethod
})
</script>

<!-- 父组件:Parent.vue -->
<template>
  <!-- 绑定子组件 ref -->
  <Child ref="childRef" />
  <!-- 绑定 DOM ref -->
  <div ref="domRef">DOM 元素</div>
  <button @click="handleAccessChild">访问子组件</button>
</template>

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

// 子组件 ref
const childRef = ref(null)
// DOM ref
const domRef = ref(null)

// 组件挂载后才能访问 ref(DOM/子组件已创建)
onMounted(() => {
  console.log('DOM 元素:', domRef.value)
})

const handleAccessChild = () => {
  // 访问子组件暴露的属性
  console.log('子组件计数:', childRef.value.childCount)
  // 调用子组件暴露的方法
  childRef.value.childMethod()
}
</script>
 

四、Vue3 进阶特性

1. 生命周期钩子

Vue3 组合式 API 的生命周期钩子与 Options API 对应,需从 vue 中导入后使用,核心钩子如下:
组合式 API 钩子 Options API 钩子 触发时机
onBeforeMount beforeMount 组件挂载前(DOM 未生成)
onMounted mounted 组件挂载后(DOM 已生成,可访问 DOM)
onBeforeUpdate beforeUpdate 组件数据更新前(DOM 未更新)
onUpdated updated 组件数据更新后(DOM 已更新)
onBeforeUnmount beforeDestroy/beforeUnmount 组件销毁前(仍可访问组件实例)
onUnmounted destroyed/unmounted 组件销毁后(清理定时器 / 事件监听等)
示例:
vue
 
 
 
 
 
<script setup>
import { ref, onMounted, onUpdated, onUnmounted } from 'vue'

const count = ref(0)
let timer = null

// 组件挂载后启动定时器
onMounted(() => {
  console.log('组件挂载完成')
  timer = setInterval(() => {
    count.value++
  }, 1000)
})

// 组件更新后触发
onUpdated(() => {
  console.log('组件数据更新了')
})

// 组件销毁前清除定时器
onUnmounted(() => {
  console.log('组件即将销毁')
  clearInterval(timer)
})
</script>
 

2. Teleport:传送门

3. Suspense:异步组件加载

4. Pinia:Vue3 官方状态管理库(替代 Vuex)

Pinia 是 Vue3 推荐的状态管理库,相比 Vuex 更简洁、更易用,支持 TypeScript 友好。

(1)安装 Pinia

bash
 
运行
 
 
 
 
npm install pinia
# 或 yarn add pinia
 

(2)全局注册 Pinia

javascript
 
运行
 
 
 
 
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import { createPinia } from 'pinia'

const app = createApp(App)
// 创建 Pinia 实例并挂载
app.use(createPinia())
app.mount('#app')
 

(3)定义 Store

在 src/store 目录下创建 Store 文件(如 userStore.js)
javascript
 
运行
 
 
 
 
// src/store/userStore.js
import { defineStore } from 'pinia'

// 定义并导出 Store(id 必须唯一,用于区分不同 Store)
export const useUserStore = defineStore('user', {
  // 状态:存储全局数据(类似 Vuex 的 state)
  state: () => ({
    username: '张三',
    token: '',
    userInfo: null
  }),

  // 计算属性:根据 state 推导新数据(类似 Vuex 的 getters,缓存结果)
  getters: {
    // 获取用户名大写形式
    upperCaseUsername: (state) => {
      return state.username.toUpperCase()
    }
  },

  // 方法:修改 state 数据(类似 Vuex 的 mutations + actions,支持同步/异步)
  actions: {
    // 同步修改用户名
    updateUsername(newName) {
      this.username = newName // 直接通过 this 访问 state 属性
    },

    // 异步获取用户信息(模拟接口请求)
    async fetchUserInfo() {
      // 模拟接口请求
      const res = await new Promise(resolve => {
        setTimeout(() => {
          resolve({
            id: 1,
            name: '李四',
            age: 20
          })
        }, 1000)
      })
      this.userInfo = res
      this.token = 'mock-token-123456'
    }
  }
})
 

(4)使用 Store

在组件中导入并使用 Store
vue
 
 
 
 
 
<template>
  <div>
    <div>用户名:{{ userStore.username }}</div>
    <div>大写用户名:{{ userStore.upperCaseUsername }}</div>
    <div>用户信息:{{ userStore.userInfo }}</div>
    <button @click="userStore.updateUsername('王五')">修改用户名</button>
    <button @click="userStore.fetchUserInfo()">获取用户信息</button>
    <button @click="resetStore">重置 Store</button>
  </div>
</template>

<script setup>
// 导入 Store
import { useUserStore } from '@/store/userStore'
// 创建 Store 实例
const userStore = useUserStore()

// 重置 Store(恢复初始状态)
const resetStore = () => {
  userStore.$reset()
}

// 也可通过解构获取 state 属性(需用 storeToRefs 保持响应式)
// import { storeToRefs } from 'pinia'
// const { username, upperCaseUsername } = storeToRefs(userStore)
</script>
 

5. Vue Router 4:Vue3 路由管理器

(1)安装 Vue Router 4

bash
 
运行
 
 
 
 
npm install vue-router@4
# 或 yarn add vue-router@4
 

(2)配置路由

在 src/router 目录下创建 index.js
javascript
 
运行
 
 
 
 
// src/router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 导入组件
import Home from '@/views/Home.vue'
import About from '@/views/About.vue'
import Detail from '@/views/Detail.vue'

// 路由规则
const routes = [
  {
    path: '/',
    name: 'Home',
    component: Home
  },
  {
    path: '/about',
    name: 'About',
    component: About
  },
  {
    // 动态路由(传递参数)
    path: '/detail/:id',
    name: 'Detail',
    component: Detail,
    // 路由传参:props 形式(推荐)
    props: true
  }
]

// 创建路由实例
const router = createRouter({
  history: createWebHistory(), // HTML5 历史模式(无 # 号)
  routes
})

export default router
 

(3)全局挂载路由

javascript
 
运行
 
 
 
 
// src/main.js
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'

const app = createApp(App)
app.use(router)
app.mount('#app')
 

(4)使用路由

  1. 路由出口与导航
    vue
     
     
     
     
     
    <!-- src/App.vue -->
    <template>
      <div id="app">
        <!-- 路由导航(替代 a 标签) -->
        <nav>
          <router-link to="/">首页</router-link>
          <router-link to="/about">关于我们</router-link>
          <router-link :to="{ name: 'Detail', params: { id: 1 } }">详情页</router-link>
        </nav>
        <!-- 路由出口:匹配的组件将渲染在这里 -->
        <router-view />
      </div>
    </template>
    
     
     
  2. 组件中操作路由
    vue
     
     
     
     
     
    <!-- src/views/Detail.vue -->
    <template>
      <div>
        <h2>详情页</h2>
        <div>当前 ID:{{ id }}</div>
        <button @click="goBack">返回上一页</button>
        <button @click="goHome">返回首页</button>
      </div>
    </template>
    
    <script setup>
    // 1. 通过 props 接收动态路由参数
    const props = defineProps({
      id: {
        type: [String, Number],
        required: true
      }
    })
    
    // 2. 导入路由实例
    import { useRouter, useRoute } from 'vue-router'
    const router = useRouter() // 路由操作实例(跳转、后退等)
    const route = useRoute() // 路由信息实例(获取参数、路径等)
    
    // 打印当前路由参数
    console.log('路由参数:', route.params.id)
    
    // 路由跳转
    const goHome = () => {
      // 方式1:路径跳转
      // router.push('/')
      // 方式2:命名路由跳转(推荐,无需关心路径)
      router.push({ name: 'Home' })
    }
    
    // 后退上一页
    const goBack = () => {
      router.go(-1)
    }
    </script>
    
     
     
  3. 路由守卫:控制路由访问权限(如登录拦截)
    javascript
     
    运行
     
     
     
     
    // src/router/index.js
    // 全局前置守卫:每次路由跳转前触发
    router.beforeEach((to, from, next) => {
      // to:目标路由;from:当前路由;next:放行函数
      const token = localStorage.getItem('token')
      // 如需登录的路由(这里假设 /about 需要登录)
      if (to.name === 'About' && !token) {
        // 未登录,跳转到首页
        next({ name: 'Home' })
      } else {
        // 已登录或无需登录,正常放行
        next()
      }
    })
    
     
     

五、实战小案例:TodoList

整合 Vue3 核心知识点,实现一个简易待办事项功能:
vue
 
 
 
 
 
<template>
  <div class="todo-container" style="width: 400px; margin: 50px auto;">
    <h2>Todo List</h2>
    <!-- 添加待办 -->
    <div class="add-todo">
      <input
        v-model="todoTitle"
        placeholder="请输入待办事项"
        @keyup.enter="addTodo"
        style="width: 300px; padding: 8px;"
      />
      <button @click="addTodo" style="padding: 8px 16px; margin-left: 8px;">添加</button>
    </div>
    <!-- 待办列表 -->
    <ul class="todo-list" style="margin-top: 20px; padding: 0; list-style: none;">
      <li
        v-for="item in todoList"
        :key="item.id"
        style="padding: 10px; border: 1px solid #eee; margin-bottom: 8px; display: flex; justify-content: space-between;"
      >
        <span :style="{ textDecoration: item.done ? 'line-through' : 'none' }">
          {{ item.title }}
        </span>
        <div>
          <button
            @click="toggleTodo(item.id)"
            style="margin-right: 8px; cursor: pointer;"
          >
            {{ item.done ? '取消' : '完成' }}
          </button>
          <button @click="deleteTodo(item.id)" style="cursor: pointer; color: red;">
            删除
          </button>
        </div>
      </li>
    </ul>
    <!-- 统计信息 -->
    <div class="todo-stats" style="margin-top: 20px;">
      剩余待办:{{ remainingCount }} / 总数量:{{ todoList.length }}
    </div>
  </div>
</template>

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

// 待办标题
const todoTitle = ref('')
// 待办列表
const todoList = ref([
  { id: 1, title: '学习 Vue3 基础', done: false },
  { id: 2, title: '掌握组合式 API', done: true }
])

// 计算属性:剩余待办数量
const remainingCount = computed(() => {
  return todoList.value.filter(item => !item.done).length
})

// 添加待办
const addTodo = () => {
  if (!todoTitle.value.trim()) return
  const newTodo = {
    id: Date.now(), // 用时间戳作为唯一 ID
    title: todoTitle.value.trim(),
    done: false
  }
  todoList.value.push(newTodo)
  todoTitle.value = '' // 清空输入框
}

// 切换待办状态
const toggleTodo = (id) => {
  const todo = todoList.value.find(item => item.id === id)
  if (todo) {
    todo.done = !todo.done
  }
}

// 删除待办
const deleteTodo = (id) => {
  todoList.value = todoList.value.filter(item => item.id !== id)
}
</script>
 

六、学习资源与常见问题

1. 官方学习资源

2. 常见问题

  1. Q:ref 数据为什么在脚本中需要 .value,模板中不需要?
     
    A:ref 本质是一个包装对象,.value 是其内部存储值的属性;Vue 在模板编译时,会自动解析 ref 的 .value,因此模板中无需手动书写。
  2. Q:reactive 解构后丢失响应式怎么办?
     
    A:使用 toRefs 或 toRef 将 reactive 对象转换为 ref 格式,解构后仍保持响应式。
  3. Q:<script setup> 中如何暴露组件属性 / 方法给父组件?
     
    A:使用 defineExpose 手动暴露,父组件通过 ref 引用访问。
  4. Q:Pinia 与 Vuex 的区别?
     
    A:Pinia 无需区分 mutations 和 actions(仅用 actions 支持同步 / 异步),无需嵌套模块,更简洁,天然支持 TypeScript,是 Vue3 官方推荐。

3. 进阶学习方向

  1. TypeScript + Vue3(类型安全,生产项目必备)
  2. Vue3 组件库(Element Plus、Ant Design Vue 等)
  3. 项目打包优化(Vite 构建优化、懒加载、CDN 加速等)
  4. 服务端渲染(Nuxt3,基于 Vue3 的 SSR 框架)