Skip to content

Vue 3 全面总结

目录

  1. 响应式系统
  2. 组合式 API (Composition API)
  3. 生命周期钩子
  4. 核心内置组件
  5. 自定义指令
  6. 依赖注入
  7. 渲染函数与虚拟 DOM
  8. 高级响应式工具
  9. 服务端渲染 (SSR)
  10. 底层原理

1. 响应式系统

1.1 ref - 基础引用

ref() 用于创建响应式引用,可包含任何类型的值。

javascript
import { ref } from 'vue'

// 原始值
const count = ref(0)
const message = ref('Hello')

// 访问和修改需要通过 .value
count.value++
console.log(count.value) // 1

// 对象类型(仍然是浅响应)
const user = ref({ name: 'John', age: 30 })
user.value.age = 31

// 模板中自动解包,不需要 .value
// <div>{{ count }}</div>

1.2 reactive - 深度响应式对象

reactive() 创建一个深度响应式的对象代理。

javascript
import { reactive } from 'vue'

const state = reactive({
  count: 0,
  user: {
    name: 'John',
    profile: { age: 30 }
  }
})

// 直接访问,不需要 .value
state.count++
state.user.profile.age = 31

// 深度响应式 - 嵌套对象也会变成响应式

重要注意事项:

javascript
// ❌ 解构会丢失响应性
const { count } = state // count 不再是响应式的

// ✅ 使用 toRefs 保持响应性
import { toRefs } from 'vue'
const { count, user } = toRefs(state) // 保持响应连接

1.3 computed - 计算属性

创建缓存的、派生的响应式值。

javascript
import { ref, computed } from 'vue'

const firstName = ref('John')
const lastName = ref('Doe')

// 只读计算属性(自动缓存)
const fullName = computed(() => {
  console.log('Computing...') // 只在依赖变化时执行
  return `${firstName.value} ${lastName.value}`
})

console.log(fullName.value) // "John Doe"
console.log(fullName.value) // 缓存,不重新计算

firstName.value = 'Jane'
console.log(fullName.value) // "Jane Doe" - 重新计算

// 可写计算属性
const fullNameWritable = computed({
  get() {
    return `${firstName.value} ${lastName.value}`
  },
  set(newValue) {
    const [first, last] = newValue.split(' ')
    firstName.value = first
    lastName.value = last || ''
  }
})

fullNameWritable.value = 'Alice Smith'

1.4 watch 与 watchEffect

watch - 精确监听

javascript
import { ref, watch } from 'vue'

const count = ref(0)

// 监听单个 ref
watch(count, (newValue, oldValue) => {
  console.log(`Count changed from ${oldValue} to ${newValue}`)
})

// 监听多个源
const name = ref('John')
watch([count, name], ([newCount, newName], [oldCount, oldName]) => {
  console.log('Multiple sources changed')
})

// 监听计算属性或 getter
watch(
  () => count.value * 2,
  (newValue, oldValue) => {
    console.log('Derived value changed')
  }
)

// 选项配置
watch(count, (newValue, oldValue) => {
  console.log('Changed')
}, {
  immediate: true,      // 立即执行回调
  deep: true,           // 深度监听对象/数组
  flush: 'post'         // 'pre' | 'post' | 'sync'
})

watchEffect - 自动追踪依赖

javascript
import { ref, watchEffect } from 'vue'

const count = ref(0)

// 立即执行并自动追踪所有访问的响应式数据
const stop = watchEffect((onCleanup) => {
  console.log(`Count is: ${count.value}`)
  
  // 清理函数
  onCleanup(() => {
    console.log('Effect cleanup')
  })
})

count.value++ // 自动触发

// 停止监听
stop()

watch vs watchEffect 对比:

特性watchwatchEffect
依赖追踪显式指定自动追踪
初始执行需要 immediate立即执行
接收旧值
性能控制更精确可能追踪多余依赖

2. 组合式 API (Composition API)

2.1 setup 函数

javascript
import { defineComponent, ref } from 'vue'

export default defineComponent({
  props: {
    title: String,
    count: {
      type: Number,
      default: 0
    }
  },
  emits: ['increment', 'decrement'],
  
  setup(props, context) {
    // props 是只读的响应式对象
    console.log(props.title)
    
    // context 包含:attrs, slots, emit
    const { emit, slots, attrs } = context
    
    // 暴露给模板
    return {
      message: ref('Hello'),
      increment() {
        emit('increment')
      }
    }
  }
})

2.2 script setup 语法糖

推荐使用的简洁语法:

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

// Props - 声明式
defineProps({
  title: String,
  count: {
    type: Number,
    default: 0
  }
})

// Props - 类型化(需 TypeScript)
// defineProps<{ title: string; count?: number }>()

// Emits
defineEmits(['increment', 'decrement'])

// 所有顶层变量自动暴露给模板
const message = ref('Hello')
const double = computed(() => props.count * 2)

// 方法
function handleClick() {
  console.log('Clicked')
}

// 模板插槽
const slots = defineSlots()

// 具名插槽暴露
defineOptions({
  name: 'MyComponent'
})
</script>

<template>
  <div @click="handleClick">
    <h1>{{ title }}</h1>
    <p>{{ message }}</p>
    <p>{{ double }}</p>
  </div>
</template>

2.3 Composables (组合函数)

可复用的组合式逻辑:

javascript
// useCounter.js
import { ref, computed } from 'vue'

export function useCounter(initialValue = 0) {
  const count = ref(initialValue)
  
  const double = computed(() => count.value * 2)
  
  const increment = () => count.value++
  const decrement = () => count.value--
  const reset = () => count.value = initialValue
  
  return {
    count,
    double,
    increment,
    decrement,
    reset
  }
}

// 在组件中使用
<script setup>
import { useCounter } from './useCounter'

const { count, double, increment, decrement } = useCounter(10)
</script>

3. 生命周期钩子

3.1 Composition API 生命周期

javascript
import {
  ref,
  onBeforeMount,
  onMounted,
  onBeforeUpdate,
  onUpdated,
  onBeforeUnmount,
  onUnmounted,
  onErrorCaptured,
  onActivated,
  onDeactivated
} from 'vue'

export default {
  setup() {
    const count = ref(0)
    
    // beforeCreate - setup 之前,无法访问
    // created - setup 函数本身相当于 created
    
    onBeforeMount(() => {
      console.log('挂载前 - DOM 尚未准备')
    })
    
    onMounted(() => {
      console.log('已挂载 - DOM 就绪')
      // 可以安全访问 DOM
    })
    
    onBeforeUpdate(() => {
      console.log('更新前 - 状态已变,DOM 未更新')
    })
    
    onUpdated(() => {
      console.log('已更新 - DOM 反映新状态')
    })
    
    onBeforeUnmount(() => {
      console.log('卸载前 - 清理定时器、监听器')
    })
    
    onUnmounted(() => {
      console.log('已卸载 - 组件从 DOM 移除')
    })
    
    // 错误捕获(捕获子组件错误)
    onErrorCaptured((err, instance, info) => {
      console.error('子组件错误:', err, info)
      return false // 阻止错误向上传播
    })
    
    // KeepAlive 专属
    onActivated(() => {
      console.log('从缓存激活')
    })
    
    onDeactivated(() => {
      console.log('失活到缓存')
    })
    
    return { count }
  }
}

3.2 生命周期时序图

[父 onBeforeMount] → [子 onBeforeMount] → [子 onMounted] → [父 onMounted]

[状态变化] → [onBeforeUpdate] → [DOM 更新] → [onUpdated]

[卸载] → [onBeforeUnmount] → [子卸载] → [onUnmounted]

4. 核心内置组件

4.1 Transition - 过渡动画

vue
<template>
  <!-- 元素显示/隐藏过渡 -->
  <Transition name="fade">
    <div v-if="show">内容</div>
  </Transition>

  <!-- 动态组件过渡 -->
  <Transition name="fade" mode="out-in" appear>
    <component :is="currentView" />
  </Transition>

  <!-- 列表项过渡 -->
  <TransitionGroup tag="ul" name="slide">
    <li v-for="item in items" :key="item.id">
      {{ item.text }}
    </li>
  </TransitionGroup>
</template>

<style>
/* CSS 类名过渡 */
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.5s ease;
}

.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

/* 列表过渡 */
.slide-move,
.slide-enter-active,
.slide-leave-active {
  transition: all 0.5s ease;
}

.slide-enter-from {
  opacity: 0;
  transform: translateX(-30px);
}

.slide-leave-to {
  opacity: 0;
  transform: translateX(30px);
}
</style>

Transition Props:

Prop说明
name过渡名称
appear初始化时也应用过渡
mode模式:in-out | out-in | default
css是否使用 CSS 过渡
duration持续时间
enter-from-class进入起始类名
leave-active-class离开激活类名

4.2 Teleport - 传送门

将内容渲染到 DOM 其他位置:

vue
<template>
  <!-- 传送到 #app 下的 #modal-container -->
  <Teleport to="#modal-container">
    <div class="modal">
      <p>模态框内容</p>
      <button @click="close">关闭</button>
    </div>
  </Teleport>

  <!-- 条件禁用传送 -->
  <Teleport to="#popup" :disabled="displayVideoInline">
    <video src="./movie.mp4" />
  </Teleport>

  <!-- Vue 3.5+ 延迟解析目标 -->
  <Teleport defer to="#late-div">
    内容
  </Teleport>
  
  <!-- 稍后定义的目标 -->
  <div id="late-div"></div>
</template>

4.3 KeepAlive - 组件缓存

缓存组件实例以保留状态:

vue
<template>
  <KeepAlive :include="['Home', 'Settings']" :exclude="['About']">
    <component :is="currentComponent" />
  </KeepAlive>
</template>

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

const currentComponent = ref('Home')
</script>

KeepAlive Props:

Prop说明
include缓存的组件名(字符串/正则/数组)
exclude不缓存的组件名
max最大缓存数量

配合生命周期使用:

javascript
import { onActivated, onDeactivated } from 'vue'

onActivated(() => {
  console.log('组件被显示(从缓存恢复)')
  // 重新获取数据等
})

onDeactivated(() => {
  console.log('组件被隐藏(保存到缓存)')
  // 暂停轮询等
})

4.4 Suspense - 异步依赖

处理异步组件加载:

vue
<template>
  <Suspense timeout="1000">
    <!-- 默认内容 -->
    <template #default>
      <AsyncComponent />
    </template>
    
    <!-- 加载中回退 -->
    <template #fallback>
      <div>Loading...</div>
    </template>
  </Suspense>
</template>

Suspense Events:

vue
<Suspense 
  @pending="onPending"
  @resolve="onResolve"
  @fallback="onFallback"
>
  <template #default>
    <AsyncComponent />
  </template>
  <template #fallback>
    <div>Loading...</div>
  </template>
</Suspense>

5. 自定义指令

5.1 注册方式

javascript
import { createApp } from 'vue'

const app = createApp({})

// 全局注册
app.directive('focus', {
  mounted(el) {
    el.focus()
  }
})

// 局部注册
export default {
  directives: {
    focus: {
      mounted(el) {
        el.focus()
      }
    }
  }
}

5.2 指令钩子

javascript
const exampleDirective = {
  // 绑定只调用一次
  created(binding, vnode) {
    console.log('指令绑定,元素还未插入')
  },
  
  // 元素插入 DOM
  beforeMount(binding, vnode) {
    console.log('元素插入前')
  },
  
  // 元素插入 DOM 后
  mounted(binding, vnode) {
    console.log('元素已插入')
    // 常用:操作 DOM
  },
  
  // 父组件 VNode 更新前
  beforeUpdate(binding, vnode, prevVnode) {
    console.log('父组件更新前')
  },
  
  // 父组件及其子组件 VNode 都更新后
  updated(binding, vnode, prevVnode) {
    console.log('父组件更新后')
  },
  
  // 元素卸载前
  beforeUnmount(binding, vnode) {
    console.log('元素卸载前')
    // 清理工作
  },
  
  // 元素及子树卸载后
  unmounted(binding, vnode) {
    console.log('元素已卸载')
  }
}

5.3 binding 对象

javascript
const myDirective = {
  mounted(el, binding) {
    console.log(binding.name)     // 'my'
    console.log(binding.value)    // 指令值
    console.log(binding.arg)      // 参数,v-el:year 中的 year
    console.log(binding.modifiers) // 修饰符对象 { focus: true }
    console.log(binding.instance)  // ❌ Vue 3 不再提供
  }
}

5.4 简写形式

javascript
// 函数形式 - 等价于 mounted 和 updated
app.directive('color', (el, binding) => {
  el.style.color = binding.value
})

// 使用
<div v-color="'red'">红色文本</div>
<div v-color:bg="'blue'">蓝色背景</div>

5.5 实战示例

javascript
// 防抖指令
app.directive('debounce', {
  mounted(el, binding) {
    let timer = null
    const handler = (...args) => {
      clearTimeout(timer)
      timer = setTimeout(() => {
        binding.value(...args)
      }, binding.arg || 300)
    }
    el.addEventListener('click', handler)
    
    el._unregister = () => {
      el.removeEventListener('click', handler)
      clearTimeout(timer)
    }
  },
  unmounted(el) {
    el._unregister?.()
  }
})

// 使用
<button v-debounce:500="handleClick">点击</button>

6. 依赖注入

6.1 provide / inject 基础

vue
<!-- 父组件 -->
<script setup>
import { provide, ref, readonly } from 'vue'

const theme = ref('light')
const toggleTheme = () => {
  theme.value = theme.value === 'light' ? 'dark' : 'light'
}

// 提供响应式数据(建议用 readonly 保护)
provide('theme', readonly(theme))
provide('toggleTheme', toggleTheme)

// 提供普通值
provide('appName', 'My App')
</script>
vue
<!-- 子组件(任意层级) -->
<script setup>
import { inject } from 'vue'

// 注入
const theme = inject('theme')
const toggleTheme = inject('toggleTheme')

// 带默认值
const appName = inject('appName', 'Default App')

// 必须传入符号表示非必填,默认值为函数
const config = inject('config', () => ({ debug: false }), true)
</script>

6.2 类型安全的 InjectionKey

typescript
// types.ts
import { InjectionKey, Ref } from 'vue'

export interface ThemeContext {
  theme: Ref<string>
  toggleTheme: () => void
}

export const ThemeKey: InjectionKey<ThemeContext> = Symbol('theme')
typescript
// Provider.vue
import { provide, readonly } from 'vue'
import { ThemeKey } from './types'

provide(ThemeKey, {
  theme: readonly(theme),
  toggleTheme
})

// Consumer.vue
import { inject } from 'vue'
import { ThemeKey } from './types'

const themeContext = inject(ThemeKey)
// themeContext.theme.value 有类型提示

6.3 实战:权限控制

javascript
// usePermission.js
import { provide, inject, ref, computed } from 'vue'

const PermissionKey = Symbol('permission')

export function usePermissionProvider(roles) {
  const userRoles = ref(roles)
  
  const hasPermission = (requiredRole) => {
    return userRoles.value.includes(requiredRole)
  }
  
  provide(PermissionKey, {
    roles: readonly(userRoles),
    hasPermission
  })
}

export function usePermission() {
  const perms = inject(PermissionKey)
  if (!perms) {
    throw new Error('Permission not provided!')
  }
  return perms
}

// 使用
// 根组件
usePermissionProvider(['admin', 'user'])

// 子组件
const { hasPermission } = usePermission()
if (hasPermission('admin')) {
  // 显示管理员功能
}

7. 渲染函数与虚拟 DOM

7.1 h 函数基础

javascript
import { h } from 'vue'

// h(type, props, children)

// 创建元素
const div = h('div', { class: 'container' }, 'Hello World')

// 多个子节点
const list = h('ul', { id: 'my-list' }, [
  h('li', 'Item 1'),
  h('li', 'Item 2'),
  h('li', 'Item 3')
])

// 事件处理
const button = h('button', {
  class: 'btn',
  onClick: () => console.log('clicked')
}, 'Click me')

7.2 纯 render 函数组件

javascript
import { h, defineComponent, ref } from 'vue'

const Counter = defineComponent({
  setup() {
    const count = ref(0)
    
    return () => {
      return h('div', { class: 'counter' }, [
        h('p', `Count: ${count.value}`),
        h('button', {
          onClick: () => count.value++
        }, 'Increment'),
        h('button', {
          onClick: () => count.value--
        }, 'Decrement')
      ])
    }
  }
})

7.3 组件作为子节点

javascript
import { h, defineComponent } from 'vue'

const Child = defineComponent({
  props: ['message'],
  setup(props) {
    return () => h('div', props.message)
  }
})

const Parent = defineComponent({
  setup() {
    return () => h('div', [
      h('h1', 'Parent Component'),
      h(Child, { message: 'Hello from parent' }),
      h(Child, { message: 'Another child' })
    ])
  }
})

7.4 插槽实现

javascript
const Card = defineComponent({
  setup(props, { slots }) {
    return () => h('div', { class: 'card' }, [
      h('header', { class: 'card-header' }, slots.header?.()),
      h('main', { class: 'card-body' }, slots.default?.()),
      h('footer', { class: 'card-footer' }, slots.footer?.())
    ])
  }
})

// 使用(render 函数中)
h(Card, {}, {
  header: () => h('h3', 'Title'),
  default: () => h('p', 'Content'),
  footer: () => h('span', 'Footer')
})

7.5 虚拟 DOM 结构

javascript
// VNode 基本结构
{
  __v_isVNode: true,
  type: 'div',           // 元素类型或组件
  props: { class: 'foo' },
  key: 'unique-key',
  ref: someRef,
  children: ['text'],    // 子节点
  suspense: null,
  ssContent: null,
  ssFallback: null,
  scopeId: null,
  slotScopeIds: null,
  axis: undefined
}

7.6 createVNode

javascript
import { createVNode, render } from 'vue'

// 手动创建并挂载 VNode
const root = document.getElementById('app')
const vnode = createVNode('div', { class: 'hello' }, 'Hello Vue 3')
render(vnode, root)

8. 高级响应式工具

8.1 shallowRef / shallowReactive

浅层响应式,只追踪第一层:

javascript
import { shallowRef, shallowReactive } from 'vue'

// shallowRef - 只追踪 .value 变化
const component = shallowRef({
  nested: { value: 1 }
})

component.value.nested.value = 2  // ❌ 不触发
component.value = {}              // ✅ 触发

// shallowReactive - 只追踪对象第一层
const state = shallowReactive({
  nested: { count: 0 }
})

state.nested.count++   // ❌ 不触发
state.nested = {}      // ✅ 触发

// 适用场景:大型不可变数据结构、第三方库实例

8.2 toRef / toRefs

保持解构时的响应性:

javascript
import { reactive, toRefs, toRef } from 'vue'

const state = reactive({
  firstName: 'John',
  lastName: 'Doe',
  age: 30
})

// toRefs - 转换所有属性为 ref
const { firstName, lastName, age } = toRefs(state)
firstName.value = 'Jane'  // state.firstName 也变为 'Jane'

// toRef - 单个属性
const nameRef = toRef(state, 'firstName')
nameRef.value = 'Bob'     // state.firstName 也变为 'Bob'

// toRef - 带默认值
const nickname = toRef(state, 'nickname', 'Unknown')

// toRef - 计算 getter
const fullName = toRef(() => `${state.firstName} ${state.lastName}`)

8.3 readonly / shallowReadonly

创建不可变代理:

javascript
import { reactive, readonly, shallowReadonly } from 'vue'

const original = reactive({
  count: 0,
  nested: { value: 1 }
})

// readonly - 深度只读
const copy = readonly(original)
// copy.count++           // ❌ 警告
// copy.nested.value++    // ❌ 警告

original.count++
console.log(copy.count)  // 1 - 仍然反映原值变化

// shallowReadonly - 只读第一层
const shallowCopy = shallowReadonly(original)
// shallowCopy.count++       // ❌ 警告
shallowCopy.nested.value++   // ✅ 允许(内层不是只读)

8.4 markRaw

标记对象永不响应式:

javascript
import { reactive, markRaw } from 'vue'

const state = reactive({
  mutable: { foo: 1 },
  immutable: markRaw({ bar: 2 })
})

state.mutable.foo = 2    // ✅ 响应式
state.immutable.bar = 3  // ✅ 但不会触发响应式更新

// 适用场景:
// 1. 不应该转为代理的性能敏感对象
// 2. 第三方类实例
// 3. 已经自带响应式系统的对象

8.5 customRef

自定义 ref 的追踪和触发逻辑:

javascript
import { customRef } from 'vue'

function useDebouncedRef(value, delay = 200) {
  let timer = null
  
  return customRef((track, trigger) => {
    let currentValue = value
    
    return {
      get() {
        track()
        return currentValue
      },
      set(newValue) {
        clearTimeout(timer)
        timer = setTimeout(() => {
          currentValue = newValue
          trigger()
        }, delay)
      }
    }
  })
}

// 使用
const debouncedSearch = useDebouncedRef('', 500)
debouncedSearch.value = 'hello'  // 500ms 后才触发更新

8.6 v-memo 指令

Vue 3.2+ 手动缓存:

vue
<template>
  <!-- 只在 searchQuery 变化时重新渲染 -->
  <div v-memo="[searchQuery]">
    <Item 
      v-for="item in filteredItems" 
      :key="item.id"
      :search-query="searchQuery"
    />
  </div>
  
  <!-- 无 memo - 每次父组件更新都会重新渲染 -->
  <div>
    <Item v-for="item in items" :key="item.id" />
  </div>
</template>

9. 服务端渲染 (SSR)

9.1 基础 SSR

javascript
// server.js
import { createSSRApp, ref, h } from 'vue'
import { renderToString } from '@vue/server-renderer'

const App = {
  setup() {
    const msg = ref('Hello Server!')
    return { msg }
  },
  template: `<div>{{ msg }}</div>`
}

async function render() {
  const app = createSSRApp(App)
  const html = await renderToString(app)
  console.log(html) // '<div>Hello Server!</div>'
  return html
}

9.2 流式渲染

javascript
import { renderToNodeStream } from '@vue/server-renderer'
import { Readable } from 'stream'

// Node.js 流
function streamRender(res) {
  const app = createSSRApp(App)
  const stream = renderToNodeStream(app)
  
  res.setHeader('Content-Type', 'text/html')
  stream.pipe(res)
}

// Web Streams API
function webStreamRender() {
  const app = createSSRApp(App)
  const stream = renderToWebStream(app)
  
  return new Response(stream, {
    headers: { 'Content-Type': 'text/html' }
  })
}

9.3 SSR Context

javascript
import { renderToString, createSSRApp } from 'vue'
import { renderToNodeStream } from '@vue/server-renderer'

async function renderWithContext() {
  const ctx = {}
  const app = createSSRApp(App)
  
  const html = await renderToString(app, ctx)
  
  // ctx.teleports 包含 teleport 的内容
  const teleports = ctx.teleports || {}
  
  return `
    <!DOCTYPE html>
    <html>
      <head>
        ${teleports.head || ''}
      </head>
      <body>
        <div id="app">${html}</div>
        ${teleports.body || ''}
      </body>
    </html>
  `
}

9.4 Teleport 在 SSR 中

vue
<!-- 组件中使用 -->
<Teleport to="body">
  <div class="modal">Modal content</div>
</Teleport>

<!-- SSR 输出会在 ctx.teleports 中收集 -->

10. 底层原理

10.1 响应式系统原理

Vue 3 使用 ES6 Proxy 实现响应式:

javascript
// 简化版实现
const targetsMap = new WeakMap()
let activeEffect = null

function track(target, key) {
  if (activeEffect) {
    let effects = targetsMap.get(target)
    if (!effects) {
      effects = new Map()
      targetsMap.set(target, effects)
    }
    let deps = effects.get(key)
    if (!deps) {
      deps = new Set()
      effects.set(key, deps)
    }
    deps.add(activeEffect)
  }
}

function trigger(target, key) {
  const effects = targetsMap.get(target)
  if (effects) {
    const deps = effects.get(key)
    if (deps) {
      deps.forEach(effect => effect())
    }
  }
}

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key) {
      track(target, key)
      const value = target[key]
      return typeof value === 'object' && value !== null 
        ? reactive(value) 
        : value
    },
    set(target, key, value) {
      target[key] = value
      trigger(target, key)
      return true
    }
  })
}

function effect(fn) {
  const runner = () => {
    activeEffect = runner
    fn()
    activeEffect = null
  }
  runner()
  return runner
}

// 使用
const data = reactive({ count: 0 })

effect(() => {
  console.log('count:', data.count)
})

data.count = 1  // 触发 effect

10.2 ref 实现原理

javascript
function isRef(value) {
  return !!value && value.__v_isRef === true
}

function ref(value) {
  const refObject = {
    __v_isRef: true,
    get value() {
      // 读取时追踪依赖
      track(refObject, 'value')
      return value
    },
    set value(newValue) {
      if (newValue !== value) {
        value = newValue
        // 写入时触发更新
        trigger(refObject, 'value')
      }
    }
  }
  return refObject
}

function unref(value) {
  return isRef(value) ? value.value : value
}

10.3 computed 实现原理

javascript
function computed(getter, setter) {
  const objectRef = reactive({})
  let result
  let dirty = true
  
  const effectFunc = () => {
    result = getter()
    dirty = false
  }
  
  effectFunc()
  
  return Object.assign(() => {
    if (dirty) {
      effectFunc()
    }
    track(objectRef, 'value')
    return result
  }, {
    get value() {
      if (dirty) {
        effectFunc()
      }
      track(objectRef, 'value')
      return result
    },
    set value(val) {
      setter?.(val)
    },
    __v_isRef: true,
    _cacheEffect: effectFunc
  })
}

10.4 虚拟 DOM Diff 算法

Vue 3 的 Diff 算法优化:

1. 双端比较
   - 头头比较
   - 尾尾比较
   - 头尾交叉比较
   - 尾头交叉比较

2. Keyed 节点 Diff
   - 使用新旧节点 key 映射
   - 最长递增子序列优化

3. Patch Flags 优化
   - TEXT: 只有文本变化
   - CLASS: 只有 class 变化
   - STYLE: 只有 style 变化
   - PROPS: 只有 props 变化
   - FULL_PROPS: 所有 props 可能变化
javascript
// Patch Flags 示例
h('div', { class: 'foo' }, [-1 /* PATCH_KEYED_FRAGMENT */, [
  h('span', { key: 'a' }, 'A'),
  h('span', { key: 'b' }, 'B')
]])

10.5 编译优化

Tree Shaking

javascript
// 只导入需要的 API
import { ref, computed } from 'vue'
// 未使用的 API 会被 tree-shaking 移除

静态提升

vue
<!-- 源码 -->
<div><span>静态文本</span></div>

<!-- 编译后 -->
const _hoisted_1 = { class: "foo" }
const _hoisted_2 = resolveComponent('span')
const _hoisted_3 = ["静态文本"]

return () => h('div', _hoisted_1, [
  h(_hoisted_2, null, _hoisted_3)
])

补丁标志

vue
<!-- 源码 -->
<div :class="activeClass">{{ text }}</div>

<!-- 编译后带有 patchFlag -->
h('div', {
  class: activeClass
}, text, 1 /* TEXT */)

10.6 Fragment、Teleport、Suspense 处理

javascript
// Fragment - 多根节点
const vnode = createVNode(Fragment, null, [
  createVNode('div', null, 'Header'),
  createVNode('div', null, 'Main'),
  createVNode('div', null, 'Footer')
])

// Teleport
const teleportVNode = createVNode(Teleport, { to: 'body' }, [
  createVNode('div', null, 'Teleported content')
])

// Suspense
const suspenseVNode = createVNode(Suspense, null, {
  default: createVNode(asyncComponent),
  fallback: createVNode('div', null, 'Loading...')
})

附录:常用 API 速查

响应式 API

API说明
ref创建响应式引用
reactive创建响应式对象
readonly创建只读代理
shallowRef浅层响应式引用
shallowReactive浅层响应式对象
shallowReadonly浅层只读代理
toRef创建属性 ref
toRefs转换为 refs 对象
customRef自定义 ref
triggerRef触发 ref 更新
unref解包 ref
isRef检查是否为 ref
isReactive检查是否响应式
isReadonly检查是否只读
isProxy检查是否为代理
markRaw标记为非响应式
toRaw获取原始对象
effectScope创建作用域
getCurrentScope获取当前作用域
onScopeDispose作用域清理

计算与监视

API说明
computed计算属性
watch精确监视
watchEffect自动追踪监视
watchPostEffect下一个 DOM 更新后运行
watchSyncEffect同步运行

生命周期

API说明
onBeforeMount挂载前
onMounted挂载后
onBeforeUpdate更新前
onUpdated更新后
onBeforeUnmount卸载前
onUnmounted卸载后
onErrorCaptured错误捕获
onActivated激活(KeepAlive)
onDeactivated失活(KeepAlive)
onServerPrefetch服务端预获取

组件相关

API说明
defineComponent定义组件
defineProps定义 props(setup)
defineEmits定义 emits(setup)
defineExpose暴露属性
defineOptions定义选项(setup)
defineSlots获取插槽(setup)
defineModel定义双向绑定(setup)
getCurrentInstance获取当前实例
withDefaultsprops 默认值
withModifiers修饰事件
withKeys键修饰符

工具函数

API说明
nextTick下一个 DOM 更新周期
h创建 VNode
resolveComponent解析组件
resolveDirective解析指令
mergeProps合并 props
cloneVNode克隆 VNode
normalizeClass规范化 class
normalizeStyle规范化 style
openBlock开启块追踪
setBlockTracking设置块追踪
createElementBlock创建元素块
createBlock创建块

文档生成日期:2026 年 4 月 15 日基于 Vue 3.x 官方文档整理

更新于:

note