核心知识点
1. Vue 3 简介
1.1 什么是 Vue 3?
Vue (发音为 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与单片框架不同,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。另一方面,当与现代化的工具链以及各种支持类库结合使用时,Vue 也完全能够为复杂的单页应用 (SPA) 提供驱动。
Vue 3 是 Vue.js 的最新主版本,它在 Vue 2 的基础上进行了诸多改进和重构,带来了更好的性能、更小的包体积、更优秀的 TypeScript 支持、新的 API(如 Composition API)以及一些新功能(如 Teleport, Suspense)。
1.2 Vue 3 的主要优势与改进
- 性能提升: 通过更优化的虚拟 DOM (Virtual DOM) 算法、编译时优化(静态节点提升、补丁标志等)实现更快的渲染和更新。
 - 更小的体积: 通过摇树优化 (Tree-shaking) 移除未使用的代码,核心库体积更小。
 - Composition API: 一种新的、基于函数的逻辑组织方式,更利于代码复用和组织复杂组件逻辑,与 Options API 并存。
 - 更好的 TypeScript 支持: 源码使用 TypeScript 重写,提供一流的类型推导和支持。
 - 新特性: 如 Teleport (瞬移组件到 DOM 其他位置)、Suspense (处理异步组件加载状态)、Fragments (模板允许多个根节点)。
 - 改进的响应式系统: 基于 ES6 Proxy 实现,性能更好,能检测到更多类型的变化(如属性添加/删除、数组索引修改)。
 
2. 创建 Vue 应用
2.1 使用 Vite 创建 (推荐)
Vite 是 Vue 官方推荐的现代前端构建工具,提供极速的冷启动和热模块替换 (HMR)。
# 使用 npm
npm create vue@latest
# 使用 yarn
# yarn create vue
# 使用 pnpm
# pnpm create vue
# 根据提示选择项目配置 (TypeScript, JSX, Router, Pinia 等)
cd <your-project-name>
npm install
npm run dev
2.2 应用实例与根组件
createApp 用于创建一个新的 Vue 应用实例。它接受一个根组件作为参数。
// main.js (或 main.ts)
import { createApp } from "vue";
import App from "./App.vue"; // 根组件
import "./style.css"; // 全局样式 (可选)
// 创建应用实例
const app = createApp(App);
// 可以在这里挂载插件、全局组件等
// app.use(...)
// app.component(...)
// app.directive(...)
// 挂载应用到 DOM 元素
app.mount("#app");
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vue 3 App</title>
  </head>
  <body>
    <!-- 应用将挂载到这里 -->
    <div id="app"></div>
    <!-- 引入入口 JS 文件 -->
    <script type="module" src="/src/main.js"></script>
  </body>
</html>
<!-- src/App.vue (根组件示例) -->
<template>
  <h1>{{ message }}</h1>
  <p>Welcome to your Vue 3 App!</p>
</template>
<script setup>
import { ref } from "vue";
const message = ref("Hello Vue 3!");
</script>
<style scoped>
h1 {
  color: #42b983;
}
</style>
3. 模板语法 (Template Syntax)
Vue 使用一种基于 HTML 的模板语法,允许开发者声明式地将 DOM 绑定至底层 Vue 实例的数据。
3.1 文本插值
使用 "Mustache" 语法(双大括号 {{ }})进行数据绑定。
<template>
  <span>Message: {{ msg }}</span>
</template>
<script setup>
import { ref } from "vue";
const msg = ref("This is a message.");
</script>
3.2 Raw HTML (v-html)
双大括号会将数据解释为普通文本,而非 HTML 代码。为了输出真正的 HTML,需要使用 v-html 指令。注意:在网站上动态渲染任意 HTML 是非常危险的,容易导致 XSS 攻击。请只对可信内容使用 v-html,永远不要用在用户提交的内容上。
<template>
  <p>Using text interpolation: {{ rawHtml }}</p>
  <p>Using v-html directive: <span v-html="rawHtml"></span></p>
</template>
<script setup>
import { ref } from "vue";
const rawHtml = ref('<span style="color: red;">This should be red.</span>');
</script>
3.3 Attribute 绑定 (v-bind 或 :)
Mustache 语法不能作用在 HTML attribute 上,需要使用 v-bind 指令或其简写 :。
<template>
  <!-- 绑定 id -->
  <div v-bind:id="dynamicId">Div with dynamic ID</div>
  <!-- 简写 -->
  <button :disabled="isButtonDisabled">Click Me</button>
  <!-- 动态绑定多个 attribute -->
  <div v-bind="objectOfAttrs">Div with multiple attributes</div>
</template>
<script setup>
import { ref, reactive } from "vue";
const dynamicId = ref("my-element-id");
const isButtonDisabled = ref(true);
const objectOfAttrs = reactive({
  id: "my-div",
  class: "container",
  "data-custom": "value",
});
// Example: Toggle button disabled state after 2 seconds
setTimeout(() => {
  isButtonDisabled.value = false;
}, 2000);
</script>
3.4 使用 JavaScript 表达式
在 {{ }} 和 v-bind 中可以支持完整的 JavaScript 表达式,但仅限于单个表达式。
<template>
  <p>{{ number + 1 }}</p>
  <p>{{ ok ? "YES" : "NO" }}</p>
  <p>{{ message.split("").reverse().join("") }}</p>
  <div :id="'list-' + id">ID: {{ id }}</div>
  <!-- 不能使用语句,如 if/else 或 for 循环 -->
  <!-- {{ var a = 1 }} -->
  <!-- {{ if (ok) { return message } }} -->
</template>
<script setup>
import { ref } from "vue";
const number = ref(5);
const ok = ref(true);
const message = ref("hello");
const id = ref(123);
</script>
3.5 指令 (Directives)
指令是带有 v- 前缀的特殊 attribute。指令 attribute 的值预期是单个 JavaScript 表达式。
3.5.1 条件渲染 (v-if, v-else-if, v-else, v-show)
v-if,v-else-if,v-else: 根据表达式的真假值来条件性地渲染一块内容。元素及内部指令会被销毁和重建。v-show: 根据表达式的真假值来切换元素的 CSSdisplay属性。元素始终会被渲染并保留在 DOM 中。v-show不支持<template>元素,也不能和v-else一起使用。
<template>
  <!-- v-if -->
  <button @click="toggleVif">Toggle v-if</button>
  <h2 v-if="awesome">Vue is awesome!</h2>
  <h2 v-else>Oh no 😢</h2>
  <!-- v-if with v-else-if -->
  <div v-if="type === 'A'">Type A</div>
  <div v-else-if="type === 'B'">Type B</div>
  <div v-else-if="type === 'C'">Type C</div>
  <div v-else>Not A/B/C</div>
  <!-- v-show -->
  <button @click="toggleVshow">Toggle v-show</button>
  <h2 v-show="okVshow">Hello with v-show!</h2>
</template>
<script setup>
import { ref } from "vue";
const awesome = ref(true);
const type = ref("A");
const okVshow = ref(true);
function toggleVif() {
  awesome.value = !awesome.value;
}
function toggleVshow() {
  okVshow.value = !okVshow.value;
}
// Example to change type
setTimeout(() => {
  type.value = "B";
}, 2000);
</script>
3.5.2 列表渲染 (v-for)
基于源数据多次渲染元素或模板块。
<template>
  <h4>Array Iteration</h4>
  <ul>
    <!-- item in items -->
    <li v-for="item in items" :key="item.id">
      {{ item.message }}
    </li>
    <!-- (item, index) in items -->
    <li v-for="(item, index) in items" :key="item.id">
      {{ index }} - {{ item.message }}
    </li>
  </ul>
  <h4>Object Iteration</h4>
  <ul>
    <!-- (value, key, index) in object -->
    <li v-for="(value, key, index) in myObject" :key="key">
      {{ index }}. {{ key }}: {{ value }}
    </li>
  </ul>
  <h4>Range Iteration</h4>
  <span v-for="n in 5" :key="n">{{ n }} </span>
  <h4>v-for with v-if</h4>
  <ul>
    <template v-for="item in items" :key="item.id">
      <li v-if="!item.isHidden">
        {{ item.message }}
      </li>
    </template>
    <!-- Note: Avoid using v-if and v-for on the same element due to precedence ambiguity.
         Use a <template> tag with v-for, and v-if on the inner element. -->
  </ul>
</template>
<script setup>
import { ref, reactive } from "vue";
const items = ref([
  { id: 1, message: "Foo", isHidden: false },
  { id: 2, message: "Bar", isHidden: true },
  { id: 3, message: "Baz", isHidden: false },
]);
const myObject = reactive({
  title: "How to do lists in Vue",
  author: "Jane Doe",
  publishedAt: "2023-01-01",
});
</script>
key: 使用 v-for 时,强烈建议提供一个 key attribute,其值必须是字符串或数字类型,并且在兄弟节点中必须唯一。key 帮助 Vue 跟踪每个节点的身份,从而重用和重新排序现有元素,提高性能。
3.5.3 事件处理 (v-on 或 @)
监听 DOM 事件,并在触发时运行一些 JavaScript 代码。
<template>
  <!-- Inline handler -->
  <button v-on:click="count++">Add 1 (Inline)</button>
  <p>Count is: {{ count }}</p>
  <!-- Method handler -->
  <button @click="greet">Greet</button>
  <!-- Method with event argument -->
  <button @click="say('hello')">Say hello</button>
  <button @click="say('bye')">Say bye</button>
  <!-- Event modifiers -->
  <!-- .stop: 阻止事件冒泡 -->
  <div @click="handleOuterClick">
    <button @click.stop="handleInnerClick">Click Me (Stop Propagation)</button>
  </div>
  <!-- .prevent: 阻止默认事件 (例如表单提交) -->
  <form @submit.prevent="onSubmit">
    <button type="submit">Submit (Prevent Default)</button>
  </form>
  <!-- .once: 只触发一次 -->
  <button @click.once="handleOnce">Click Once</button>
  <!-- .self: 只在事件是从侦听器绑定的元素本身触发时才触发 -->
  <div @click.self="handleSelf">Outer Div (Self)<span>Inner Span</span></div>
  <!-- Key modifiers -->
  <input @keyup.enter="submitOnEnter" placeholder="Press Enter" />
  <input @keyup.alt.enter="clearInput" placeholder="Press Alt+Enter" />
</template>
<script setup>
import { ref } from "vue";
const count = ref(0);
function greet(event) {
  alert("Hello!");
  // `event` 是原生 DOM 事件
  if (event) {
    console.log(event.target.tagName); // BUTTON
  }
}
function say(message) {
  alert(message);
}
function handleOuterClick() {
  console.log("Outer div clicked");
}
function handleInnerClick() {
  console.log("Inner button clicked (propagation stopped)");
}
function onSubmit() {
  console.log("Form submitted (default prevented)");
}
function handleOnce() {
  console.log("Clicked once!");
}
function handleSelf() {
  console.log("Outer div clicked directly (self)");
}
function submitOnEnter() {
  console.log("Enter key pressed on input");
}
function clearInput(event) {
  event.target.value = "";
  console.log("Alt+Enter pressed, input cleared");
}
</script>
3.5.4 表单输入绑定 (v-model)
在表单 <input>, <textarea>, <select> 元素上创建双向数据绑定。它会根据控件类型自动选取正确的方法来更新元素。
<template>
  <h4>Text Input</h4>
  <input type="text" v-model="message" placeholder="edit me" />
  <p>Message is: {{ message }}</p>
  <h4>Textarea</h4>
  <textarea
    v-model="multiLineMessage"
    placeholder="add multiple lines"
  ></textarea>
  <p style="white-space: pre-line;">
    Multiline message is:\n{{ multiLineMessage }}
  </p>
  <h4>Checkbox (single)</h4>
  <input type="checkbox" id="checkbox" v-model="checked" />
  <label for="checkbox">{{ checked }}</label>
  <h4>Checkbox (multiple)</h4>
  <div>Checked names: {{ checkedNames }}</div>
  <input type="checkbox" id="jack" value="Jack" v-model="checkedNames" />
  <label for="jack">Jack</label>
  <input type="checkbox" id="john" value="John" v-model="checkedNames" />
  <label for="john">John</label>
  <input type="checkbox" id="mike" value="Mike" v-model="checkedNames" />
  <label for="mike">Mike</label>
  <h4>Radio</h4>
  <div>Picked: {{ picked }}</div>
  <input type="radio" id="one" value="One" v-model="picked" />
  <label for="one">One</label>
  <input type="radio" id="two" value="Two" v-model="picked" />
  <label for="two">Two</label>
  <h4>Select (single)</h4>
  <div>Selected: {{ selected }}</div>
  <select v-model="selected">
    <option disabled value="">Please select one</option>
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <h4>Select (multiple)</h4>
  <div>Selected multiple: {{ multiSelected }}</div>
  <select v-model="multiSelected" multiple style="width:100px;">
    <option>A</option>
    <option>B</option>
    <option>C</option>
  </select>
  <h4>v-model Modifiers</h4>
  <!-- .lazy: 在 change 事件而非 input 事件更新 -->
  <input v-model.lazy="lazyMsg" placeholder="Lazy update" />
  <p>Lazy: {{ lazyMsg }}</p>
  <!-- .number: 输入值转为数值类型 -->
  <input v-model.number="age" type="number" placeholder="Number" />
  <p>Age (type): {{ typeof age }}</p>
  <!-- .trim: 自动过滤输入首尾空格 -->
  <input v-model.trim="trimmedMsg" placeholder="Trim whitespace" />
  <p>Trimmed: '{{ trimmedMsg }}'</p>
</template>
<script setup>
import { ref } from "vue";
const message = ref("");
const multiLineMessage = ref("");
const checked = ref(true);
const checkedNames = ref(["Jack"]); // Needs to be an array for multiple checkboxes
const picked = ref("One");
const selected = ref("");
const multiSelected = ref(["A"]); // Needs to be an array for multiple select
const lazyMsg = ref("");
const age = ref(0);
const trimmedMsg = ref("");
</script>
3.5.5 Class 与 Style 绑定 (:class, :style)
动态地绑定 HTML class 和 style。
<template>
  <h4>Class Bindings</h4>
  <!-- Object syntax -->
  <div :class="{ active: isActive, 'text-danger': hasError }">
    Object Syntax Class
  </div>
  <!-- Array syntax -->
  <div :class="[activeClass, errorClass]">Array Syntax Class</div>
  <!-- With plain class -->
  <div class="static" :class="{ active: isActive }">Static + Object Syntax</div>
  <button @click="toggleClass">Toggle Classes</button>
  <h4>Style Bindings</h4>
  <!-- Object syntax -->
  <div :style="{ color: activeColor, fontSize: fontSize + 'px' }">
    Object Syntax Style
  </div>
  <!-- Use camelCase or kebab-case (in quotes) -->
  <div :style="{ 'font-weight': 'bold', backgroundColor: bgColor }">
    Object Syntax Style (mixed case)
  </div>
  <!-- Array syntax (multiple style objects) -->
  <div :style="[baseStyles, overridingStyles]">Array Syntax Style</div>
  <button @click="changeStyle">Change Style</button>
</template>
<script setup>
import { ref, reactive, computed } from "vue";
// Class binding refs
const isActive = ref(true);
const hasError = ref(false);
const activeClass = ref("active");
const errorClass = ref("text-danger");
function toggleClass() {
  isActive.value = !isActive.value;
  hasError.value = !hasError.value;
}
// Style binding refs
const activeColor = ref("red");
const fontSize = ref(20);
const bgColor = ref("lightyellow");
const baseStyles = reactive({
  color: "blue",
  fontSize: "16px",
});
const overridingStyles = reactive({
  fontWeight: "bold",
  border: "1px solid blue",
});
function changeStyle() {
  activeColor.value = activeColor.value === "red" ? "green" : "red";
  fontSize.value += 2;
}
</script>
<style>
.static {
  font-style: italic;
}
.active {
  font-weight: bold;
  border: 1px solid green;
}
.text-danger {
  color: red;
}
</style>
4. 响应式核心 (Reactivity Fundamentals)
Vue 的核心特性之一是其响应式系统。当数据变化时,视图会自动更新。Vue 3 使用 ES6 Proxy 实现响应式。
4.1 ref()
接受一个内部值并返回一个响应式且可变的 ref 对象。ref 对象仅有一个 .value property,指向该内部值。用于使基本类型值 (String, Number, Boolean 等) 或单个对象/数组引用具有响应性。
<template>
  <p>Count: {{ count }}</p>
  <button @click="increment">Increment</button>
</template>
<script setup>
import { ref } from "vue";
// 创建一个 ref,初始值为 0
const count = ref(0);
console.log(count.value); // 0 (在 <script> 中访问需要 .value)
function increment() {
  // 在 <script> 中修改需要 .value
  count.value++;
}
// 在 <template> 中使用时,会自动解包 (unwrap),无需 .value
// 所以模板中可以直接用 {{ count }}
</script>
4.2 reactive()
返回一个对象的响应式代理。它深层地将对象内部的所有嵌套 property 都转换为响应式。主要用于使对象或数组具有响应性。注意: reactive() 不能处理基本类型值,并且解构 reactive 对象会使其失去响应性。
<template>
  <p>User: {{ state.user.name }} ({{ state.user.age }})</p>
  <p>Skills: {{ state.skills.join(", ") }}</p>
  <button @click="updateUser">Update User</button>
  <button @click="addSkill">Add Skill</button>
</template>
<script setup>
import { reactive } from "vue";
// 创建一个响应式对象
const state = reactive({
  user: {
    name: "Alice",
    age: 30,
  },
  skills: ["Vue", "JavaScript"],
});
function updateUser() {
  // 可以直接修改对象的属性
  state.user.name = "Bob";
  state.user.age++;
  // 也可以添加新属性,它也是响应式的
  state.user.city = "New York";
}
function addSkill() {
  // 修改数组也是响应式的
  state.skills.push("TypeScript");
}
// 错误示例:解构会失去响应性
// let { name, age } = state.user; // name 和 age 不再是响应式的
// name = 'Charlie'; // 不会更新视图
// 正确处理解构需要 toRefs()
// import { toRefs } from 'vue'
// const { user, skills } = toRefs(state);
// user.value.name = 'Charlie'; // 这样可以
</script>
4.3 computed()
用于声明计算属性。计算属性会基于其响应式依赖进行缓存。只有在相关响应式依赖发生改变时它们才会重新求值。
<template>
  <p>First Name: <input v-model="firstName" /></p>
  <p>Last Name: <input v-model="lastName" /></p>
  <p>Full Name (Computed): {{ fullName }}</p>
  <p>Full Name (Method): {{ getFullName() }}</p>
  <button @click="changeFirstName">Change First Name</button>
</template>
<script setup>
import { ref, computed } from "vue";
const firstName = ref("John");
const lastName = ref("Doe");
// 定义计算属性 fullName
const fullName = computed(() => {
  console.log("Computed fullName recalculated"); // 只在 firstName 或 lastName 改变时执行
  return firstName.value + " " + lastName.value;
});
// 对比:使用方法
function getFullName() {
  console.log("Method getFullName called"); // 每次模板渲染都会执行
  return firstName.value + " " + lastName.value;
}
function changeFirstName() {
  firstName.value = "Jane";
}
// 计算属性默认是 getter-only,也可以提供 setter
// const fullNameWithSetter = computed({
//   get() {
//     return firstName.value + ' ' + lastName.value;
//   },
//   set(newValue) {
//     [firstName.value, lastName.value] = newValue.split(' ');
//   }
// });
// fullNameWithSetter.value = 'Peter Jones'; // 会调用 setter
</script>
4.4 watch() 与 watchEffect()
用于侦听响应式数据的变化并执行副作用 (Side Effects),如 API 调用、DOM 操作等。
watch():- 懒执行: 默认情况下,仅在侦听源发生变化时才执行回调。
 - 需要明确指定要侦听的响应式源 (ref, reactive 对象, getter 函数, 或包含这些的数组)。
 - 可以访问变化前后的值。
 - 提供更多配置选项 (如 
immediate,deep)。 
watchEffect():- 立即执行: 初始化时会立即执行一次回调函数,然后在其依赖项变化时重新运行。
 - 自动追踪依赖: 无需指定侦听源,它会自动追踪在回调函数中访问过的响应式依赖。
 - 无法访问变化前的值。
 
<template>
  <div>
    <p>Watch Source (Ref): <input v-model="watchSourceRef" /></p>
    <p>
      Watch Source (Reactive Prop):
      <input v-model="watchSourceReactive.nested.value" />
    </p>
    <p>Watch Log: {{ watchLog }}</p>
    <p>WatchEffect Log: {{ effectLog }}</p>
  </div>
</template>
<script setup>
import { ref, reactive, watch, watchEffect } from "vue";
const watchSourceRef = ref("initial ref");
const watchSourceReactive = reactive({ nested: { value: "initial reactive" } });
const watchLog = ref("");
const effectLog = ref("");
// --- watch examples ---
// 1. Watching a ref
watch(watchSourceRef, (newValue, oldValue) => {
  watchLog.value = `Ref changed from '${oldValue}' to '${newValue}'`;
  console.log("Watch (ref):", newValue, oldValue);
});
// 2. Watching a getter function for a reactive property
watch(
  () => watchSourceReactive.nested.value, // Use a getter for specific reactive property
  (newValue, oldValue) => {
    watchLog.value = `Reactive prop changed from '${oldValue}' to '${newValue}'`;
    console.log("Watch (getter):", newValue, oldValue);
  }
);
// 3. Watching a reactive object directly (requires deep: true for nested changes)
// watch(watchSourceReactive, (newValue, oldValue) => {
//   // Note: newValue and oldValue will be the same object reference for reactive objects!
//   console.log('Watch (reactive object - deep):', newValue, oldValue);
//   watchLog.value = `Reactive object changed (nested value: ${newValue.nested.value})`;
// }, { deep: true }); // Deep watch needed for nested properties
// 4. Immediate watch
// watch(watchSourceRef, (newValue) => {
//   console.log('Immediate Watch (ref):', newValue);
// }, { immediate: true });
// --- watchEffect example ---
watchEffect(() => {
  // This runs immediately and whenever watchSourceRef or watchSourceReactive.nested.value changes
  // because they are accessed inside the effect function.
  const message = `Effect ran. Ref: '${watchSourceRef.value}', Reactive: '${watchSourceReactive.nested.value}'`;
  effectLog.value = message;
  console.log("watchEffect ran");
});
// Example change after 2 seconds
setTimeout(() => {
  watchSourceRef.value = "updated ref";
  watchSourceReactive.nested.value = "updated reactive";
}, 2000);
</script>
5. Composition API
Composition API 是一系列 API 的集合,允许我们使用导入的函数而不是声明选项 (Options API) 来编写 Vue 组件。它提供了更灵活、更可组合的代码组织方式。
5.1 setup() 函数 (Composition API 入口点 - 可选)
setup 函数是组件中使用 Composition API 的入口点。它在组件实例创建之前执行。
- 参数: 接收 
props和context(包含attrs,slots,emit)。 - 返回值: 返回的对象或 ref 会暴露给模板和组件实例。
 this: 在setup中this不可用 (为undefined)。
注意: 随着 <script setup> 的引入,显式使用 setup() 函数的场景已经大大减少,但理解其工作原理仍然有帮助。
<!-- ComponentWithOptionsSetup.vue -->
<template>
  <p>Message from setup: {{ message }}</p>
  <p>Prop value: {{ myProp }}</p>
  <button @click="increment">Increment from setup</button>
</template>
<script>
import { ref, toRefs, watch } from "vue";
export default {
  props: {
    myProp: {
      type: String,
      required: true,
    },
  },
  // Explicit setup function
  setup(props, context) {
    console.log("setup() executed");
    // props is reactive, but destructuring it directly loses reactivity
    // Use toRefs or access via props.myProp
    const { myProp } = toRefs(props); // Maintain reactivity
    // Define reactive state
    const message = ref("Hello from setup!");
    const count = ref(0);
    // Define methods
    function increment() {
      count.value++;
      message.value = `Count is ${count.value}`;
      // Emit event using context.emit
      context.emit("incremented", count.value);
    }
    // Watch props
    watch(myProp, (newVal) => {
      console.log("Prop changed in setup:", newVal);
    });
    // Expose state and methods to the template
    return {
      message,
      increment,
      // myProp is automatically available in the template via props
    };
  },
  // Options API can still be used alongside setup()
  data() {
    return {
      optionsData: "Data from Options API",
    };
  },
  mounted() {
    console.log("mounted() hook from Options API");
    console.log("Accessing setup ref from options:", this.message); // Can access exposed refs
  },
};
</script>
5.2 <script setup> (推荐语法)
是 setup() 函数的编译时语法糖。提供了更简洁、更符合人体工程学的 Composition API 用法。
- 在 
<script setup>中声明的顶层绑定(包括变量、函数声明、以及import引入的内容)都能在模板中直接使用。 - 无需显式返回任何内容。
 props,emit,attrs,slots需要通过defineProps,defineEmits,useAttrs,useSlots等编译器宏来访问。
<!-- MyComponentScriptSetup.vue -->
<template>
  <h2>Using <script setup></h2>
  <p>Message: {{ message }}</p>
  <p>Prop value: {{ myProp }}</p>
  <p>Double count: {{ doubleCount }}</p>
  <button @click="increment">Increment</button>
</template>
<script setup>
import { ref, computed, watch, defineProps, defineEmits } from "vue";
// 1. defineProps to declare props
const props = defineProps({
  myProp: {
    type: String,
    required: true,
    default: "Default Value",
  },
});
// 2. defineEmits to declare emitted events
const emit = defineEmits(["incremented"]);
// 3. Reactive state (refs, reactive, etc.) are automatically exposed
const count = ref(0);
const message = ref("Hello from <script setup>");
// 4. Computed properties
const doubleCount = computed(() => count.value * 2);
// 5. Functions are automatically exposed
function increment() {
  count.value++;
  message.value = `Count updated to ${count.value}`;
  // Emit event using the emit function
  emit("incremented", count.value);
}
// 6. Watchers
watch(
  () => props.myProp,
  (newVal) => {
    console.log("<script setup> prop changed:", newVal);
  }
);
watch(count, (newCount) => {
  console.log("<script setup> count changed:", newCount);
});
// 7. Lifecycle hooks are imported and used directly
import { onMounted, onUnmounted } from "vue";
onMounted(() => {
  console.log("<script setup> component mounted");
});
onUnmounted(() => {
  console.log("<script setup> component unmounted");
});
// No return statement needed!
</script>
<style scoped>
/* Component-specific styles */
</style>
5.3 Lifecycle Hooks
Composition API 提供了 onX 形式的生命周期钩子函数,需要在 setup() 或 <script setup> 内同步调用。
| Options API | Composition API (setup or <script setup>) | 
|---|---|
beforeCreate | Use setup() itself | 
created | Use setup() itself | 
beforeMount | onBeforeMount | 
mounted | onMounted | 
beforeUpdate | onBeforeUpdate | 
updated | onUpdated | 
beforeUnmount | onBeforeUnmount | 
unmounted | onUnmounted | 
errorCaptured | onErrorCaptured | 
renderTracked | onRenderTracked (Debug) | 
renderTriggered | onRenderTriggered (Debug) | 
activated | onActivated (for <KeepAlive>) | 
deactivated | onDeactivated (for <KeepAlive>) | 
serverPrefetch | serverPrefetch (SSR only) | 
<script setup>
import { onMounted, onUpdated, onUnmounted, ref } from "vue";
const count = ref(0);
console.log("Component setup phase");
onMounted(() => {
  console.log("Component has been mounted!");
  // Perform setup tasks like fetching data, setting up timers/listeners
});
onUpdated(() => {
  console.log("Component has been updated!");
  // Called after the component's DOM has been updated due to reactive state changes
});
onUnmounted(() => {
  console.log("Component is about to be unmounted!");
  // Perform cleanup tasks like clearing timers, removing listeners
});
</script>
<template>
  <p>Count: {{ count }}</p>
  <button @click="count++">Update</button>
</template>
5.4 Dependency Injection (provide & inject)
允许一个祖先组件向其所有后代组件注入依赖,无论组件层次有多深。
provide(key, value): 在祖先组件中提供数据或方法。key可以是字符串或 Symbol。inject(key, defaultValue): 在后代组件中注入由祖先提供的数据或方法。可以提供一个默认值。
<!-- AncestorComponent.vue -->
<template>
  <div>
    <h2>Ancestor Component</h2>
    <button @click="changeTheme">Change Theme</button>
    <ChildComponent />
  </div>
</template>
<script setup>
import { ref, provide } from "vue";
import ChildComponent from "./ChildComponent.vue";
// 1. Provide reactive data
const theme = ref("light");
provide("theme", theme); // Provide the ref itself for reactivity
// 2. Provide a method
function changeTheme() {
  theme.value = theme.value === "light" ? "dark" : "light";
  console.log("Theme changed in ancestor:", theme.value);
}
provide("changeThemeMethod", changeTheme);
// 3. Provide static data
provide("staticData", "This data is static");
</script>
<!-- ChildComponent.vue -->
<template>
  <div class="child">
    <h3>Child Component</h3>
    <GrandChildComponent />
  </div>
</template>
<script setup>
import GrandChildComponent from "./GrandChildComponent.vue";
// Child doesn't need to know about the provided data if it doesn't use it
</script>
<style scoped>
.child {
  margin-left: 20px;
  border-left: 1px solid #ccc;
  padding-left: 10px;
}
</style>
<!-- GrandChildComponent.vue -->
<template>
  <div class="grandchild">
    <h4>GrandChild Component</h4>
    <p>Injected Theme: {{ theme }}</p>
    <p>Injected Static Data: {{ staticValue }}</p>
    <p>Data with Default: {{ nonExistentData }}</p>
    <button @click="callInjectedMethod">Change Theme from GrandChild</button>
  </div>
</template>
<script setup>
import { inject } from "vue";
// 1. Inject data provided by ancestor
const theme = inject("theme"); // Injects the reactive ref
const staticValue = inject("staticData");
// 2. Inject with a default value
const nonExistentData = inject("non-existent-key", "Default Value");
// 3. Inject the method
const callInjectedMethod = inject("changeThemeMethod");
console.log("Injected theme in grandchild:", theme.value);
</script>
<style scoped>
.grandchild {
  margin-left: 20px;
  border-left: 1px solid #eee;
  padding-left: 10px;
}
</style>
6. 组件 (Components)
组件是 Vue 的核心概念,允许将 UI 划分为独立可复用的部分。
6.1 单文件组件 (Single-File Components - SFC)
使用 .vue 文件,将组件的模板 (<template>)、逻辑 (<script>) 和样式 (<style>) 封装在一起。这是开发 Vue 应用的标准方式。
<!-- src/components/MyButton.vue -->
<template>
  <button class="my-button" @click="handleClick">
    <slot></slot>
    <!-- Default slot for button text -->
    <span v-if="count > 0"> ({{ count }})</span>
  </button>
</template>
<script setup>
import { ref, defineEmits, defineProps } from "vue";
// Define props
const props = defineProps({
  initialCount: {
    type: Number,
    default: 0,
  },
});
// Define emitted events
const emit = defineEmits(["button-click"]);
// Reactive state
const count = ref(props.initialCount);
// Method
function handleClick() {
  count.value++;
  emit("button-click", count.value); // Emit event with payload
}
</script>
<style scoped>
/* Scoped styles only apply to this component */
.my-button {
  background-color: #42b983;
  color: white;
  padding: 8px 15px;
  border: none;
  border-radius: 4px;
  cursor: pointer;
  transition: background-color 0.3s ease;
}
.my-button:hover {
  background-color: #36a374;
}
</style>
6.2 组件注册
- 全局注册: 使用 
app.component()。全局注册的组件可以在应用的任何模板中使用。应谨慎使用,避免全局污染。 - 局部注册: 在需要使用组件的父组件的 
<script setup>或components选项中导入并注册。推荐方式。 
// main.js (Global Registration - less common)
import { createApp } from "vue";
import App from "./App.vue";
import GlobalButton from "./components/GlobalButton.vue"; // Assume GlobalButton.vue exists
const app = createApp(App);
// Register GlobalButton globally
app.component("GlobalButton", GlobalButton);
app.mount("#app");
<!-- ParentComponent.vue (Local Registration - Recommended) -->
<template>
  <div>
    <h2>Parent Component</h2>
    <!-- Use the locally registered component -->
    <MyButton :initial-count="5" @button-click="onMyButtonClick">
      Click Me Locally!
    </MyButton>
    <p>Button clicked {{ clickCount }} times.</p>
    <!-- Global component can also be used here -->
    <!-- <GlobalButton>Global</GlobalButton> -->
  </div>
</template>
<script setup>
// Import the component locally
import MyButton from "./components/MyButton.vue"; // Adjust path as needed
import { ref } from "vue";
const clickCount = ref(0);
function onMyButtonClick(newCount) {
  console.log("MyButton clicked in parent, new count:", newCount);
  clickCount.value = newCount;
}
</script>
6.3 Props
Props 是父组件向子组件传递数据的方式。数据单向流动:从父到子。子组件应避免直接修改 prop。
- 声明: 使用 
defineProps宏 (在<script setup>) 或props选项。 - 类型和验证: 可以指定 prop 的类型、是否必需、默认值以及自定义验证函数。
 
<!-- ChildComponentWithProps.vue -->
<template>
  <div class="child-props">
    <h3>Child Component</h3>
    <p>Message Prop: {{ message }}</p>
    <p>Count Prop: {{ count }} (Type: {{ typeof count }})</p>
    <p>User Prop Name: {{ user?.name ?? "N/A" }}</p>
    <!-- Optional chaining -->
    <p>Is Active Prop: {{ isActive }}</p>
  </div>
</template>
<script setup>
import { defineProps, onMounted } from "vue";
const props = defineProps({
  // Basic type check (`null` and `undefined` values will allow any type)
  message: String,
  // Multiple possible types
  count: [String, Number],
  // Required string
  id: {
    type: String,
    required: true,
  },
  // Number with a default value
  level: {
    type: Number,
    default: 1,
  },
  // Object with a default value (use factory function)
  user: {
    type: Object,
    // Default factory function for objects/arrays
    default: () => ({ name: "Guest", age: 0 }),
  },
  // Boolean with default
  isActive: {
    type: Boolean,
    default: false,
  },
  // Custom validator function
  customProp: {
    validator: (value) => {
      // The value must match one of these strings
      return ["success", "warning", "danger"].includes(value);
    },
  },
});
onMounted(() => {
  console.log("Props received:", props);
  // console.log(props.message);
});
</script>
<style scoped>
.child-props {
  border: 1px solid blue;
  padding: 10px;
  margin-top: 10px;
}
</style>
<!-- ParentUsingProps.vue -->
<template>
  <div>
    <h2>Parent Using Props</h2>
    <ChildComponentWithProps
      :id="'item-123'"
      message="Hello from parent!"
      :count="42"
      :user="{ name: 'Alice', age: 30 }"
      :is-active="true"
      customProp="success"
    />
    <ChildComponentWithProps
      :id="'item-456'"
      message="Another message"
      :count="'55'"
    />
    <!-- Other props use defaults -->
    <!-- Missing required 'id' would cause a warning -->
    <!-- Invalid customProp would cause a warning -->
    <!-- <ChildComponentWithProps customProp="invalid"/> -->
  </div>
</template>
<script setup>
import ChildComponentWithProps from "./ChildComponentWithProps.vue";
</script>
6.4 自定义事件 ($emit / defineEmits)
子组件可以通过触发事件 (emit) 来与父组件通信,通常用于响应用户交互或内部状态变化。
- 声明: 使用 
defineEmits宏 (在<script setup>) 或emits选项。推荐显式声明,便于理解和类型检查。 - 触发: 在子组件中使用 
emit('eventName', ...payload)。 - 监听: 在父组件中使用 
v-on或@监听子组件触发的事件 (@eventName="handler")。 
<!-- CustomInput.vue -->
<template>
  <div>
    <label :for="id">{{ label }}: </label>
    <input
      :id="id"
      :value="modelValue"
      @input="handleInput"
      placeholder="Enter text..."
    />
  </div>
</template>
<script setup>
import { defineProps, defineEmits } from "vue";
const props = defineProps({
  modelValue: String, // Used for v-model compatibility
  label: String,
  id: {
    type: String,
    default: () => `input-${Math.random().toString(36).substr(2, 9)}`,
  },
});
// Declare the 'update:modelValue' event for v-model support
// Declare a custom 'focused' event
const emit = defineEmits(["update:modelValue", "focused"]);
function handleInput(event) {
  // Emit 'update:modelValue' for v-model
  emit("update:modelValue", event.target.value);
  // Emit custom event when focused (example)
  if (event.type === "focus") {
    // This example uses @input, add @focus if needed
    emit("focused");
  }
}
</script>
<!-- ParentUsingEvents.vue -->
<template>
  <div>
    <h2>Parent Listening to Events</h2>
    <!-- Use v-model which translates to :modelValue and @update:modelValue -->
    <CustomInput
      v-model="inputValue"
      label="My Custom Input"
      @focused="logFocus"
    />
    <p>Input Value in Parent: {{ inputValue }}</p>
  </div>
</template>
<script setup>
import { ref } from "vue";
import CustomInput from "./CustomInput.vue";
const inputValue = ref("Initial value");
function logFocus() {
  console.log("CustomInput received focus!");
}
</script>
6.5 插槽 (Slots)
允许父组件向子组件指定的位置插入内容。
- 默认插槽: 子组件使用 
<slot></slot>标签定义内容插入点。父组件在使用子组件时,放在标签内的内容会插入到默认插槽。 - 具名插槽: 子组件使用 
<slot name="slotName"></slot>定义。父组件使用<template v-slot:slotName>或#slotName来向指定插槽插入内容。 - 作用域插槽: 子组件可以在 
<slot>标签上绑定 attribute (props),父组件通过v-slot:slotName="slotProps"或#slotName="slotProps"来接收这些数据,从而可以在父组件中定义使用子组件数据的模板。 
<!-- FancyButton.vue (Child) -->
<template>
  <button class="fancy-btn">
    <!-- Default Slot -->
    <slot>Default Button Text</slot>
    <!-- Named Slot for an icon -->
    <span class="icon">
      <slot name="icon"></slot>
    </span>
    <!-- Scoped Slot passing data to parent -->
    <div class="data-area">
      <slot name="dataScope" :internalData="childData" :count="clickCount">
        <!-- Fallback content if parent doesn't provide scoped slot -->
        Fallback: {{ childData }} (Clicks: {{ clickCount }})
      </slot>
    </div>
    <button @click="clickCount++">Inc Child Count</button>
  </button>
</template>
<script setup>
import { ref } from "vue";
const childData = ref("Data from child");
const clickCount = ref(0);
</script>
<style scoped>
.fancy-btn {
  /* ... styles ... */
  padding: 10px;
  border: 1px solid #ccc;
  position: relative;
}
.icon {
  margin-left: 5px;
}
.data-area {
  margin-top: 5px;
  font-size: 0.8em;
  color: gray;
}
</style>
<!-- ParentUsingSlots.vue -->
<template>
  <div>
    <h2>Parent Using Slots</h2>
    <!-- 1. Using Default Slot -->
    <FancyButton>
      Click Me!
      <!-- This goes into the default slot -->
    </FancyButton>
    <hr />
    <!-- 2. Using Named Slots -->
    <FancyButton>
      <!-- Content for default slot -->
      Submit
      <!-- Content for named slot 'icon' -->
      <template v-slot:icon>
        🚀
        <!-- Rocket emoji -->
      </template>
      <!-- Shorthand #icon -->
      <!-- <template #icon>🚀</template> -->
    </FancyButton>
    <hr />
    <!-- 3. Using Scoped Slots -->
    <FancyButton>
      <template v-slot:dataScope="slotProps">
        <!-- Use data passed from child via slotProps -->
        Parent sees: {{ slotProps.internalData }} | Clicks:
        {{ slotProps.count }}
      </template>
      <!-- Shorthand #dataScope="{ internalData, count }" -->
      <!--
      <template #dataScope="{ internalData, count }">
        Parent sees: {{ internalData }} | Clicks: {{ count }}
      </template>
      -->
    </FancyButton>
  </div>
</template>
<script setup>
import FancyButton from "./FancyButton.vue";
</script>
6.6 Fallthrough Attributes ($attrs)
指父组件传递给子组件,但没有被子组件通过 props 或 emits 声明接收的 attribute 或 v-on 事件监听器。默认情况下,这些 attribute 会被自动添加到子组件的根元素上。可以通过 inheritAttrs: false 禁用此行为,并通过 useAttrs() (Composition API) 或 $attrs (Options API) 访问它们。
<!-- BaseInput.vue -->
<template>
  <label>
    {{ label }}
    <input
      type="text"
      :value="modelValue"
      @input="$emit('update:modelValue', $event.target.value)"
      v-bind="inputAttrs"
      <!--
      Bind
      non-prop
      attributes
      to
      the
      input
      --
    />
    >
  </label>
</template>
<script>
// Disabling inheritAttrs is common for components wrapping an input
// to avoid applying attributes like 'class' or listeners to the root label
// instead of the input itself.
export default {
  inheritAttrs: false,
};
</script>
<script setup>
import { defineProps, defineEmits, useAttrs, computed } from "vue";
defineProps({
  label: String,
  modelValue: String,
});
defineEmits(["update:modelValue"]);
const attrs = useAttrs();
// Example: Separate input-specific attributes from others
const inputAttrs = computed(() => {
  const inputOnlyAttrs = {};
  for (const key in attrs) {
    // Example: only bind attributes like placeholder, maxlength etc. to input
    if (
      ["placeholder", "maxlength", "required", "disabled", "readonly"].includes(
        key
      ) ||
      key.startsWith("data-")
    ) {
      inputOnlyAttrs[key] = attrs[key];
    }
    // If there were listeners like @focus, @blur they would also be in attrs
  }
  return inputOnlyAttrs;
});
// Non-input attrs (like class, style) are not automatically applied now.
// If needed, they could be bound to the root <label> using v-bind="labelAttrs"
</script>
<!-- ParentUsingAttrs.vue -->
<template>
  <BaseInput label="My Input" v-model="text" placeholder="Enter something..."
  maxlength="50" required class="parent-class"
  <!-- This class will NOT be applied to BaseInput's root label -->
  data-test-id="my-base-input"
  <!-- This data-* attr WILL be bound to the input -->
  @focus="onFocus"
  <!-- This listener will NOT be applied to the root label -->
  />
</template>
<script setup>
import { ref } from "vue";
import BaseInput from "./BaseInput.vue";
const text = ref("");
function onFocus() {
  console.log("BaseInput focused (listener passed via attrs)");
}
</script>
7. Advanced Features
7.1 Custom Directives
除了核心指令 (v-model, v-show 等),你还可以注册自定义指令来封装可复用的 DOM 操作。
<!-- ComponentUsingCustomDirective.vue -->
<template>
  <input v-focus placeholder="I should have focus on mount" />
  <input v-color="'red'" placeholder="My text should be red" />
  <p
    v-demo-directive:[argument].modifier1.modifier2="{
      color: 'blue',
      text: 'hello!',
    }"
  >
    Custom Directive with Args/Mods/Value
  </p>
</template>
<script setup>
import { ref, onMounted } from "vue";
// --- Local Custom Directive ---
// Simple focus directive
const vFocus = {
  // Called when the bound element's parent component is mounted
  mounted: (el) => {
    console.log("v-focus mounted on:", el);
    el.focus();
  },
};
// Directive with value binding
const vColor = {
  mounted: (el, binding) => {
    // binding.value contains the value passed to the directive
    el.style.color = binding.value;
    console.log("v-color applied with value:", binding.value);
  },
  updated: (el, binding) => {
    el.style.color = binding.value; // Handle value updates
  },
};
// Directive with arguments, modifiers, and value object
const vDemoDirective = {
  mounted(el, binding) {
    console.log("vDemoDirective Mounted");
    console.log(" Argument:", binding.arg); // e.g., 'foo' from v-demo-directive:foo
    console.log(" Modifiers:", binding.modifiers); // e.g., { modifier1: true, modifier2: true }
    console.log(" Value:", binding.value); // e.g., { color: 'blue', text: 'hello!' }
    el.style.color = binding.value.color || "black";
    el.textContent = binding.value.text || "Default Text";
    if (binding.modifiers.modifier1) {
      el.style.border = "1px solid red";
    }
  },
};
const argument = ref("foo"); // Dynamic argument example
</script>
全局注册 (在 main.js)
// import { createApp } from 'vue'
// const app = createApp(App)
// app.directive('focus', { /* ... implementation ... */ })
7.2 Teleport
将组件模板的一部分渲染到 DOM 树中的另一个位置,例如将模态框或通知直接渲染到 <body> 下,避免 CSS z-index 或 overflow 问题。
<template>
  <button @click="showModal = true">Show Modal</button>
  <!-- Teleport the modal content to the body -->
  <Teleport to="body">
    <div v-if="showModal" class="modal">
      <h2>I'm a teleported modal!</h2>
      <p>
        My parent component is somewhere else, but I render directly under body.
      </p>
      <button @click="showModal = false">Close</button>
    </div>
  </Teleport>
</template>
<script setup>
import { ref } from "vue";
const showModal = ref(false);
</script>
<style scoped>
/* Styles for the modal */
.modal {
  position: fixed;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  background-color: white;
  padding: 20px;
  border: 1px solid #ccc;
  box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
  z-index: 1000; /* Ensure it's on top */
}
</style>
7.3 Suspense (Experimental)
用于协调异步依赖(如异步组件加载、setup 中 await)的加载状态,可以在等待异步内容时显示 fallback 内容。
<!-- ParentWithSuspense.vue -->
<template>
  <h2>Handling Async Components</h2>
  <Suspense>
    <!-- Component with async setup or async component itself -->
    <AsyncComponent />
    <!-- Fallback content while loading -->
    <template #fallback>
      <div>Loading... Please wait.</div>
    </template>
  </Suspense>
</template>
<script setup>
import { defineAsyncComponent } from "vue";
// Define an async component (e.g., code-splitting)
const AsyncComponent = defineAsyncComponent(
  () => import("./MyAsyncDataComponent.vue") // Assume this component fetches data in setup
);
</script>
<!-- MyAsyncDataComponent.vue -->
<template>
  <div>
    <h3>Async Data Component</h3>
    <p v-if="error">Error loading data: {{ error }}</p>
    <ul v-else-if="data">
      <li v-for="item in data" :key="item.id">{{ item.name }}</li>
    </ul>
    <p v-else>No data loaded (should have shown loading in parent).</p>
  </div>
</template>
<script setup>
import { ref } from "vue";
const data = ref(null);
const error = ref(null);
// Simulate async data fetching in setup
async function fetchData() {
  console.log("Async component setup started...");
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      if (Math.random() > 0.1) {
        // Simulate success most of the time
        console.log("Async data fetched.");
        resolve([
          { id: 1, name: "Item A" },
          { id: 2, name: "Item B" },
        ]);
      } else {
        console.error("Async data fetching failed.");
        reject(new Error("Failed to load data"));
      }
    }, 1500); // Simulate network delay
  });
}
// Using top-level await in <script setup> makes the component async
try {
  data.value = await fetchData();
} catch (e) {
  error.value = e.message;
}
console.log("Async component setup finished.");
</script>
7.4 Composables (组合式函数)
利用 Composition API 的函数,用于封装和复用有状态逻辑。它们是普通的 JavaScript 函数,约定俗成以 use 开头,可以返回响应式状态、计算属性和方法。
// composables/useMousePosition.js
import { ref, onMounted, onUnmounted } from "vue";
// Convention: composable function names start with "use"
export function useMousePosition() {
  // State encapsulated and managed by the composable
  const x = ref(0);
  const y = ref(0);
  // A composable can update its managed state over time.
  function update(event) {
    x.value = event.pageX;
    y.value = event.pageY;
  }
  // A composable can also hook into its owner component's
  // lifecycle to set up and tear down side effects.
  onMounted(() => {
    console.log("useMousePosition mounted");
    window.addEventListener("mousemove", update);
  });
  onUnmounted(() => {
    console.log("useMousePosition unmounted");
    window.removeEventListener("mousemove", update);
  });
  // Expose managed state as return value
  return { x, y };
}
<!-- ComponentUsingComposable.vue -->
<template>
  <div>
    <h3>Mouse Position Composable</h3>
    <p>Mouse X: {{ mouseX }}</p>
    <p>Mouse Y: {{ mouseY }}</p>
  </div>
</template>
<script setup>
// Import and use the composable
import { useMousePosition } from "../composables/useMousePosition"; // Adjust path
// Call the composable to get the reactive state
const { x: mouseX, y: mouseY } = useMousePosition(); // Destructure return value
</script>
8. 生态系统
8.1 Vue Router
官方的路由管理器,用于构建单页应用 (SPA)。
- 安装: 
npm install vue-router@4 - 配置: 创建路由实例,定义路由映射,将其挂载到 Vue 应用实例。
 - 核心组件: 
<router-link>(导航),<router-view>(路由出口)。 - API: 
useRouter(访问路由器实例),useRoute(访问当前路由信息)。 
// router/index.js
import { createRouter, createWebHistory } from "vue-router";
import HomeView from "../views/HomeView.vue";
import AboutView from "../views/AboutView.vue";
const routes = [
  {
    path: "/",
    name: "home",
    component: HomeView,
  },
  {
    path: "/about",
    name: "about",
    component: AboutView,
    // Example of lazy loading:
    // component: () => import('../views/AboutView.vue')
  },
  {
    path: "/user/:id", // Dynamic route segment
    name: "user",
    component: () => import("../views/UserProfile.vue"),
    props: true, // Pass route params as props to the component
  },
];
const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL), // HTML5 History mode
  // history: createWebHashHistory(), // Hash mode (#)
  routes,
});
export default router;
// main.js
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router"; // Import the router
const app = createApp(App);
app.use(router); // Use the router plugin
app.mount("#app");
<!-- App.vue -->
<template>
  <header>
    <nav>
      <router-link to="/">Home</router-link> |
      <router-link to="/about">About</router-link> |
      <router-link :to="{ name: 'user', params: { id: '123' } }"
        >User 123</router-link
      >
    </nav>
  </header>
  <main>
    <!-- Where routed components will be rendered -->
    <router-view />
  </main>
</template>
<script setup>
// No specific script needed here for basic routing setup
</script>
<style>
/* Basic styling */
nav a.router-link-exact-active {
  color: #42b983;
  font-weight: bold;
}
</style>
<!-- views/UserProfile.vue -->
<template>
  <div>
    <h2>User Profile</h2>
    <p>User ID: {{ id }}</p>
    <p>Current Path: {{ route.path }}</p>
    <button @click="goToAbout">Go to About</button>
  </div>
</template>
<script setup>
import { defineProps } from "vue";
import { useRoute, useRouter } from "vue-router";
// Access props passed from router (if props: true)
const props = defineProps({
  id: String,
});
// Access current route information
const route = useRoute();
console.log("Current route params:", route.params); // { id: '123' }
// Access router instance for navigation
const router = useRouter();
function goToAbout() {
  router.push({ name: "about" }); // Navigate programmatically
  // router.push('/about'); // Alternative
}
</script>
8.2 Pinia
官方推荐的状态管理库,用于管理跨组件共享的状态。相比 Vuex,Pinia API 更简洁,对 TypeScript 支持更好,且更模块化。
- 安装: 
npm install pinia - 配置: 创建 Pinia 实例并挂载到 Vue 应用。
 - 定义 Store: 使用 
defineStore创建一个 Store,包含 state (类似 data), getters (类似 computed), actions (类似 methods)。 - 使用 Store: 在组件中导入并调用 store 函数。
 
// stores/counter.js
import { defineStore } from "pinia";
import { ref, computed } from "vue"; // Can use Composition API inside stores
// Option Store syntax (similar to Options API)
// export const useCounterStore = defineStore('counter', {
//   state: () => ({
//     count: 0,
//     name: 'My Counter'
//   }),
//   getters: {
//     doubleCount: (state) => state.count * 2,
//   },
//   actions: {
//     increment(amount = 1) {
//       this.count += amount; // 'this' refers to the store instance
//     },
//     reset() {
//       this.count = 0;
//     },
//   },
// });
// Setup Store syntax (using Composition API)
export const useCounterStore = defineStore("counter", () => {
  // State -> refs
  const count = ref(0);
  const name = ref("My Counter");
  // Getters -> computeds
  const doubleCount = computed(() => count.value * 2);
  // Actions -> functions
  function increment(amount = 1) {
    count.value += amount;
  }
  function reset() {
    count.value = 0;
  }
  // Return state, getters, and actions
  return { count, name, doubleCount, increment, reset };
});
// main.js
import { createApp } from "vue";
import { createPinia } from "pinia"; // Import Pinia
import App from "./App.vue";
const app = createApp(App);
const pinia = createPinia(); // Create Pinia instance
app.use(pinia); // Use the Pinia plugin
app.mount("#app");
<!-- ComponentUsingPinia.vue -->
<template>
  <div>
    <h2>Pinia Counter Store</h2>
    <p>Store Name: {{ counterStore.name }}</p>
    <p>Count: {{ counterStore.count }}</p>
    <p>Double Count: {{ counterStore.doubleCount }}</p>
    <button @click="counterStore.increment()">Increment</button>
    <button @click="counterStore.increment(5)">Increment by 5</button>
    <button @click="counterStore.reset()">Reset</button>
    <hr />
    <p>Local Count (for comparison): {{ localCount }}</p>
    <button @click="localCount++">Inc Local</button>
  </div>
</template>
<script setup>
import { useCounterStore } from "../stores/counter"; // Import the store
import { ref } from "vue";
import { storeToRefs } from "pinia"; // Utility to keep reactivity when destructuring state/getters
// Use the store
const counterStore = useCounterStore();
// Option 1: Access directly via counterStore.property (always works)
// Option 2: Destructuring (loses reactivity for state/getters without storeToRefs)
// const { count, doubleCount } = counterStore; // WRONG: count and doubleCount are not reactive here
// const { increment, reset } = counterStore; // OK: Actions are just functions
// Option 3: Destructuring with storeToRefs (Recommended for state/getters)
// const { count, doubleCount, name } = storeToRefs(counterStore);
// const { increment, reset } = counterStore; // Actions can still be destructured directly
// Local state for comparison
const localCount = ref(0);
</script>
9. 工具
9.1 Vite
下一代前端构建工具,利用浏览器原生 ES 模块导入和 esbuild(Go 编写,极快)提供极速的冷启动和即时热模块更新 (HMR)。是 Vue 3 项目的默认和推荐构建工具。
9.2 Vue Devtools
浏览器扩展,用于检查 Vue 组件树、状态 (Props, Data, Computed)、事件、路由和 Pinia/Vuex 状态,是调试 Vue 应用的必备工具。
# Typically installed automatically when creating project with `create-vue`
# If not, install Vite: npm install -D vite @vitejs/plugin-vue
# Start dev server: npm run dev
# Build for production: npm run build