Vue 3高级特性
Vue 3引入了许多高级特性,这些特性不仅提高了开发效率,还增强了框架的灵活性和扩展性。本章将详细介绍Vue 3的一些高级特性,包括自定义渲染器、Teleport传送门、Suspense异步组件和响应式系统源码分析。
15.38.1 自定义渲染器
什么是自定义渲染器
自定义渲染器是Vue 3的一个核心特性,它允许开发者将Vue的声明式API与任意平台的渲染逻辑结合起来。通过自定义渲染器,你可以将Vue组件渲染到WebGL、Canvas、原生移动应用甚至终端等非DOM环境中。
自定义渲染器的基本结构
// src/renderers/custom-renderer.js
import { createRenderer } from 'vue'
// 创建自定义渲染器
const renderer = createRenderer({
// DOM创建相关
createElement(tag) {
// 创建元素
console.log(`创建元素: ${tag}`)
return { tag }
},
// 设置元素属性
patchProp(el, key, prevValue, nextValue) {
// 设置属性
console.log(`设置属性: ${key} = ${nextValue}`)
el[key] = nextValue
},
// 插入元素
insert(el, parent, anchor = null) {
// 插入元素
console.log(`插入元素: ${el.tag} 到 ${parent.tag}`)
if (!parent.children) {
parent.children = []
}
parent.children.push(el)
},
// 移除元素
remove(el) {
// 移除元素
console.log(`移除元素: ${el.tag}`)
},
// 创建文本节点
createText(text) {
// 创建文本节点
console.log(`创建文本节点: ${text}`)
return { type: 'text', text }
},
// 设置文本内容
setText(node, text) {
// 设置文本内容
console.log(`设置文本内容: ${text}`)
node.text = text
},
// 创建注释节点
createComment(text) {
// 创建注释节点
console.log(`创建注释节点: ${text}`)
return { type: 'comment', text }
},
// 设置注释内容
setComment(node, text) {
// 设置注释内容
console.log(`设置注释内容: ${text}`)
node.text = text
},
// 父元素的第一个子节点
nextSibling(node) {
// 获取下一个兄弟节点
return null
},
// 父元素的最后一个子节点
parentNode(node) {
// 获取父节点
return null
},
// 克隆节点
cloneNode(node) {
// 克隆节点
return { ...node }
},
// 插入文档片段
insertStaticContent(content, parent, anchor, namespace, shouldCache) {
// 插入静态内容
return { remove: () => {} }
}
})
// 创建应用实例
const app = renderer.createApp({
template: `<div><h1>Hello Custom Renderer</h1><p>{{ message }}</p></div>`,
data() {
return {
message: '这是一个自定义渲染器示例'
}
}
})
// 创建一个根容器
const rootContainer = { tag: 'root' }
// 挂载应用
app.mount(rootContainer)
console.log('渲染结果:', rootContainer)自定义Canvas渲染器示例
// src/renderers/canvas-renderer.js
import { createRenderer } from 'vue'
// 创建Canvas渲染器
const createCanvasRenderer = (canvas) => {
const ctx = canvas.getContext('2d')
if (!ctx) {
throw new Error('无法获取Canvas上下文')
}
// 元素缓存
const elements = new Map()
// 绘制元素
const drawElement = (el) => {
if (el.tag === 'rect') {
ctx.fillStyle = el.fill || 'black'
ctx.fillRect(el.x || 0, el.y || 0, el.width || 100, el.height || 100)
} else if (el.tag === 'circle') {
ctx.fillStyle = el.fill || 'black'
ctx.beginPath()
ctx.arc(el.x || 50, el.y || 50, el.radius || 50, 0, Math.PI * 2)
ctx.fill()
} else if (el.tag === 'text') {
ctx.fillStyle = el.color || 'black'
ctx.font = `${el.fontSize || 16}px ${el.fontFamily || 'Arial'}`
ctx.fillText(el.text || '', el.x || 0, el.y || 20)
}
}
// 清除画布
const clearCanvas = () => {
ctx.clearRect(0, 0, canvas.width, canvas.height)
}
// 重绘所有元素
const redraw = () => {
clearCanvas()
elements.forEach(el => {
drawElement(el)
})
}
return createRenderer({
createElement(tag) {
const el = { tag }
elements.set(el, el)
return el
},
patchProp(el, key, prevValue, nextValue) {
el[key] = nextValue
redraw()
},
insert(el, parent, anchor) {
redraw()
},
remove(el) {
elements.delete(el)
redraw()
},
createText(text) {
const el = { tag: 'text', text }
elements.set(el, el)
return el
},
setText(node, text) {
node.text = text
redraw()
},
createComment(text) {
const el = { tag: 'comment', text }
return el
},
setComment(node, text) {
node.text = text
},
nextSibling(node) {
return null
},
parentNode(node) {
return null
},
cloneNode(node) {
return { ...node }
},
insertStaticContent(content, parent, anchor, namespace, shouldCache) {
return { remove: () => {} }
}
})
}
// 使用示例
const canvas = document.getElementById('canvas')
const renderer = createCanvasRenderer(canvas)
const app = renderer.createApp({
template: `
<rect x="10" y="10" width="100" height="100" fill="red" />
<circle x="150" y="60" radius="50" fill="blue" />
<text x="10" y="150" text="Hello Canvas Renderer" color="green" fontSize="20" />
`
})
app.mount(canvas)自定义渲染器的应用场景
- 跨平台开发:将Vue组件渲染到不同平台,如WebGL、Canvas、原生移动应用等
- 游戏开发:使用Vue的声明式API开发游戏界面
- 终端应用:将Vue组件渲染到终端界面
- 服务器端渲染:实现自定义的服务器端渲染逻辑
- 特殊DOM环境:在受限的DOM环境中使用Vue
15.38.2 Teleport传送门
什么是Teleport
Teleport是Vue 3的一个新特性,它允许你将组件的DOM结构"传送"到DOM树中的任何位置,而不受组件层级结构的限制。这对于创建模态框、弹出菜单、通知等组件非常有用。
Teleport的基本用法
<!-- src/components/Modal.vue -->
<template>
<Teleport to="body">
<div class="modal-overlay" v-if="show">
<div class="modal-content">
<div class="modal-header">
<h3>{{ title }}</h3>
<button @click="close" class="close-btn">×</button>
</div>
<div class="modal-body">
<slot></slot>
</div>
<div class="modal-footer">
<slot name="footer">
<button @click="close" class="btn btn-secondary">关闭</button>
</slot>
</div>
</div>
</div>
</Teleport>
</template>
<script setup lang="ts">
import { defineProps, defineEmits } from 'vue'
const props = defineProps<{
show: boolean
title?: string
}>()
const emit = defineEmits<{
(e: 'close'): void
}>()
const close = () => {
emit('close')
}
</script>
<style scoped>
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: rgba(0, 0, 0, 0.5);
display: flex;
align-items: center;
justify-content: center;
z-index: 1000;
}
.modal-content {
background-color: white;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.1);
width: 90%;
max-width: 500px;
max-height: 80vh;
overflow-y: auto;
}
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
padding: 16px;
border-bottom: 1px solid #e9ecef;
}
.modal-header h3 {
margin: 0;
font-size: 18px;
}
.close-btn {
background: none;
border: none;
font-size: 24px;
cursor: pointer;
color: #6c757d;
}
.modal-body {
padding: 16px;
}
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 8px;
padding: 16px;
border-top: 1px solid #e9ecef;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
}
.btn-secondary {
background-color: #6c757d;
color: white;
}
</style>使用Teleport组件
<!-- src/views/Home.vue -->
<template>
<div class="home">
<h1>Vue 3 Teleport示例</h1>
<button @click="showModal = true" class="btn btn-primary">打开模态框</button>
<Modal :show="showModal" title="示例模态框" @close="showModal = false">
<p>这是一个使用Teleport实现的模态框示例。</p>
<p>模态框的DOM结构会被传送到底部,而不受父组件样式的影响。</p>
<template #footer>
<button @click="showModal = false" class="btn btn-primary">确定</button>
<button @click="showModal = false" class="btn btn-secondary">取消</button>
</template>
</Modal>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue'
import Modal from '@/components/Modal.vue'
const showModal = ref(false)
</script>Teleport的高级用法
1. 动态目标
<template>
<Teleport :to="targetElement">
<div class="dynamic-teleport">
动态目标的Teleport内容
</div>
</Teleport>
<select v-model="targetId">
<option value="#target1">目标1</option>
<option value="#target2">目标2</option>
</select>
<div id="target1" class="target">目标1</div>
<div id="target2" class="target">目标2</div>
</template>
<script setup lang="ts">
import { ref, computed } from 'vue'
const targetId = ref('#target1')
const targetElement = computed(() => document.querySelector(targetId.value) || 'body')
</script>2. 多个Teleport到同一目标
<template>
<Teleport to="#shared-target">
<div class="teleport-item">Teleport内容1</div>
</Teleport>
<Teleport to="#shared-target">
<div class="teleport-item">Teleport内容2</div>
</Teleport>
<div id="shared-target" class="shared-target"></div>
</template>Teleport的注意事项
- CSS作用域:Teleport内容的样式仍然受父组件的作用域样式影响
- 事件冒泡:Teleport内容的事件会冒泡到父组件
- SSR支持:Teleport在服务器端渲染中也能正常工作
- 目标元素:确保目标元素在Teleport挂载之前已经存在
15.38.2 Teleport传送门(续)
Teleport与CSS过渡动画
Teleport可以与Vue的过渡系统结合使用,实现平滑的动画效果:
<template>
<Teleport to="body">
<Transition name="modal">
<div class="modal-overlay" v-if="show">
<div class="modal-content">
<!-- 模态框内容 -->
</div>
</div>
</Transition>
</Teleport>
</template>
<style scoped>
.modal-enter-active,
.modal-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.modal-enter-from,
.modal-leave-to {
opacity: 0;
}
.modal-enter-from .modal-content,
.modal-leave-to .modal-content {
transform: scale(0.9) translateY(-20px);
}
</style>15.38.3 Suspense异步组件
什么是Suspense
Suspense是Vue 3的一个新组件,它允许你在等待异步组件加载完成时显示一个 fallback 内容。Suspense可以处理:
- 异步组件(通过
defineAsyncComponent定义的组件) - 带有异步
setup函数的组件 - 组件树中任何层级的异步依赖
Suspense的基本用法
<!-- src/App.vue -->
<template>
<div class="app">
<h1>Vue 3 Suspense示例</h1>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div class="loading">
<el-icon class="is-loading"><loading /></el-icon>
<span>加载中...</span>
</div>
</template>
</Suspense>
</div>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import { Loading } from '@element-plus/icons-vue'
// 定义异步组件
const AsyncComponent = defineAsyncComponent({
// 加载组件
loader: () => import('./components/AsyncComponent.vue'),
// 加载超时时间
timeout: 3000,
// 加载失败时显示的组件
errorComponent: () => import('./components/ErrorComponent.vue')
})
</script>异步组件
<!-- src/components/AsyncComponent.vue -->
<template>
<div class="async-component">
<h2>异步组件</h2>
<p>{{ data.message }}</p>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from 'vue'
// 模拟异步数据获取
const fetchData = () => {
return new Promise<{ message: string }>((resolve) => {
setTimeout(() => {
resolve({ message: '异步数据加载成功!' })
}, 2000)
})
}
// 异步setup函数
const data = await fetchData()
</script>Suspense的高级用法
1. 嵌套Suspense
<template>
<Suspense>
<template #default>
<div class="outer-suspense">
<h2>外层Suspense</h2>
<Suspense>
<template #default>
<DeepAsyncComponent />
</template>
<template #fallback>
<div class="inner-loading">内层加载中...</div>
</template>
</Suspense>
</div>
</template>
<template #fallback>
<div class="outer-loading">外层加载中...</div>
</template>
</Suspense>
</template>2. 错误处理
<template>
<Suspense>
<template #default>
<ErrorBoundary>
<AsyncComponent />
</ErrorBoundary>
</template>
<template #fallback>
<div class="loading">加载中...</div>
</template>
</Suspense>
</template>
<script setup lang="ts">
import { defineAsyncComponent } from 'vue'
import ErrorBoundary from '@/components/ErrorBoundary.vue'
const AsyncComponent = defineAsyncComponent({
loader: () => import('./components/FailingComponent.vue')
})
</script><!-- src/components/ErrorBoundary.vue -->
<template>
<div v-if="error" class="error-boundary">
<h3>发生错误</h3>
<p>{{ error.message }}</p>
<button @click="resetError">重试</button>
</div>
<slot v-else></slot>
</template>
<script setup lang="ts">
import { ref, onErrorCaptured } from 'vue'
const error = ref<Error | null>(null)
const resetError = () => {
error.value = null
// 重新渲染组件
window.location.reload()
}
onErrorCaptured((err) => {
error.value = err as Error
return false
})
</script>Suspense的注意事项
- 仅处理异步依赖:Suspense只能处理组件树中的异步依赖,不能处理同步错误
- 需要异步组件或异步setup:Suspense需要配合异步组件或带有异步setup函数的组件使用
- 错误处理:Suspense本身不处理错误,需要配合ErrorBoundary组件使用
- SSR支持:Suspense在服务器端渲染中也能正常工作
15.38.4 响应式系统源码分析
Vue 3响应式系统概述
Vue 3的响应式系统是基于ES6的Proxy API实现的,它提供了更全面的响应式支持,包括:
- 对象和数组的响应式
- 嵌套对象的响应式
- Map、Set、WeakMap、WeakSet的响应式
- 计算属性和侦听器
响应式系统核心API
- **
reactive**:创建响应式对象 - **
ref**:创建响应式基本类型 - **
computed**:创建计算属性 - **
watch**:创建侦听器 - **
watchEffect**:创建响应式副作用 - **
toRefs**:将响应式对象转换为ref对象集合 - **
shallowReactive**:创建浅层响应式对象 - **
shallowRef**:创建浅层响应式基本类型 - **
readonly**:创建只读响应式对象 - **
shallowReadonly**:创建浅层只读响应式对象
响应式系统源码结构
1. 依赖收集与触发
// src/reactivity/src/effect.ts
// 依赖收集
class ReactiveEffect {
private _fn: any
deps: Dep[] = []
active = true
onStop?: () => void
constructor(fn, public scheduler?: EffectScheduler) {
this._fn = fn
}
run() {
// 运行副作用函数
if (!this.active) {
return this._fn()
}
// 依赖收集
activeEffect = this
cleanupEffect(this)
const result = this._fn()
activeEffect = undefined
return result
}
stop() {
// 停止副作用
if (this.active) {
this.active = false
cleanupEffect(this)
if (this.onStop) {
this.onStop()
}
}
}
}
// 依赖触发
function triggerEffects(dep: Dep) {
for (const effect of dep) {
if (effect.scheduler) {
effect.scheduler()
} else {
effect.run()
}
}
}2. Reactive实现
// src/reactivity/src/reactive.ts
// 创建响应式对象
function createReactiveObject(target: Target, isReadonly: boolean, baseHandlers: ProxyHandler<any>, collectionHandlers: ProxyHandler<any>) {
// 检查目标是否为对象
if (!isObject(target)) {
return target
}
// 如果目标已经是响应式对象,直接返回
if (target[ReactiveFlags.RAW] && !(isReadonly && target[ReactiveFlags.IS_REACTIVE])) {
return target
}
// 创建Proxy
const proxy = new Proxy(
target,
// 根据目标类型选择不同的处理器
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
return proxy
}
// 基础处理器
const mutableHandlers: ProxyHandler<object> = {
get(target, key, receiver) {
// 处理特殊键
if (key === ReactiveFlags.IS_REACTIVE) {
return true
}
// 依赖收集
track(target, TrackOpTypes.GET, key)
// 获取值
const res = Reflect.get(target, key, receiver)
// 递归处理嵌套对象
if (isObject(res)) {
return reactive(res)
}
return res
},
set(target, key, value, receiver) {
// 获取旧值
const oldValue = (target as any)[key]
// 设置值
const result = Reflect.set(target, key, value, receiver)
// 依赖触发
if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
return result
},
deleteProperty(target, key) {
// 检查属性是否存在
const hadKey = hasOwn(target, key)
// 删除属性
const result = Reflect.deleteProperty(target, key)
// 依赖触发
if (hadKey && result) {
trigger(target, TriggerOpTypes.DELETE, key)
}
return result
},
has(target, key) {
// 依赖收集
track(target, TrackOpTypes.HAS, key)
return Reflect.has(target, key)
},
ownKeys(target) {
// 依赖收集
track(target, TrackOpTypes.ITERATE, ITERATE_KEY)
return Reflect.ownKeys(target)
}
}3. Ref实现
// src/reactivity/src/ref.ts
// 创建ref
class RefImpl<T> {
private _value: T
private _rawValue: T
public dep?: Dep = undefined
public readonly __v_isRef = true
constructor(value: T, public readonly __v_isShallow: boolean) {
this._rawValue = __v_isShallow ? value : toRaw(value)
this._value = __v_isShallow ? value : toReactive(value)
}
get value() {
// 依赖收集
trackRefValue(this)
return this._value
}
set value(newVal) {
// 获取原始值
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal)
newVal = useDirectValue ? newVal : toRaw(newVal)
// 检查值是否变化
if (hasChanged(newVal, this._rawValue)) {
// 更新值
this._rawValue = newVal
this._value = useDirectValue ? newVal : toReactive(newVal)
// 依赖触发
triggerRefValue(this)
}
}
}响应式系统工作流程
- 创建响应式对象:使用
reactive或ref创建响应式对象 - 依赖收集:当访问响应式对象的属性时,收集依赖(副作用函数)
- 响应式更新:当修改响应式对象的属性时,触发依赖(重新运行副作用函数)
响应式系统优化
- 懒依赖收集:只有在访问响应式属性时才收集依赖
- 精准依赖触发:只有修改了被依赖的属性时才触发依赖
- 避免循环依赖:通过
toRaw函数避免循环依赖 - 浅层响应式:提供
shallowReactive和shallowRef创建浅层响应式对象 - 只读响应式:提供
readonly和shallowReadonly创建只读响应式对象
Vue 3高级特性总结
自定义渲染器:
- 允许将Vue组件渲染到任意平台
- 提高了框架的灵活性和扩展性
- 适用于跨平台开发
Teleport传送门:
- 允许将组件内容渲染到DOM树的任意位置
- 解决了模态框、弹出菜单等组件的样式隔离问题
- 支持动态目标和多个Teleport到同一目标
Suspense异步组件:
- 简化了异步组件的处理
- 提供了统一的加载状态管理
- 支持嵌套和错误处理
响应式系统:
- 基于ES6 Proxy API实现
- 支持更全面的响应式数据类型
- 提供了多种响应式API
- 依赖收集和触发机制更加高效
通过学习这些高级特性,你可以更好地理解Vue 3的设计理念和内部工作原理,从而在实际开发中更加灵活地使用Vue 3,构建出更高质量的应用程序。