Vue 3 相比于 Vue 2 做了很多改进,不仅提升了性能,还引入了一些新的功能,使得开发更加高效、灵活。
Vue 3 在性能方面做了大量的优化,尤其是在渲染和更新方面,主要通过以下几个方式提升:
这是 Vue 3 最重要的特性之一。组合式 API 为 Vue 提供了更加灵活和清晰的代码结构。它引入了一些新的 API,如 ref
、reactive
、computed
、watch
等,可以让开发者在组件中更加方便地组织和复用逻辑。
例如:
import { ref, reactive, onMounted } from 'vue';
export default {
setup() {
const count = ref(0); // 响应式数据
const state = reactive({ name: 'Vue 3' });
onMounted(() => {
console.log('组件已挂载');
});
return {
count,
state
};
}
};
ref
用来创建基础数据类型的响应式数据;reactive
用来创建对象或数组的响应式数据;onMounted
等生命周期钩子函数则让你在组合式 API 中使用组件生命周期。在 Vue 3 中,组合式 API 的引入使得生命周期钩子的命名有所变化。Vue 2 的生命周期钩子是如 beforeCreate、created、mounted 等,它们是基于选项式 API 的。Vue 3 修改了一些生命周期钩子的名称,去除了 Vue 2 中的 beforeDestroy 和 destroyed,用 beforeUnmount 和 unmounted 代替。类似地,created 被替换成了 setup,以适应 Composition API。
Vue 3 引入了生命周期钩子的组合式 API 版本,它们可以在 setup 函数中使用,如 onMounted、onUpdated、onUnmounted 等。例如:
beforeMount
→ onBeforeMount
mounted
→ onMounted
beforeUpdate
→ onBeforeUpdate
updated
→ onUpdated
这些新钩子函数是用于在 setup
函数中使用的。
Vue 3 引入了 Teleport
组件,允许你将 DOM 元素从一个地方传送到另一个地方。例如,你可以将模态框、通知等组件的内容从当前组件的 DOM 树中移到根节点或 body
中,而不必改变组件的层级结构。
<template>
<Teleport to="body">
<div class="modal">This is a modal!</div>
</Teleport>
</template>
Vue 3 支持 Fragments,即一个组件可以返回多个根节点元素,而不再需要一个单独的根元素来包裹组件。这对于优化组件结构、减少无意义的 DOM 元素非常有用。
<template>
<div>Part 1</div>
<div>Part 2</div>
</template>
在 Vue 2 中,每个组件必须有且只有一个根节点,而 Vue 3 允许组件返回多个元素。
Vue 3 引入了 Suspense
组件,用于处理异步组件的加载。它允许你在等待某个组件加载时展示一个加载指示器。
<template>
<Suspense>
<template #default>
<AsyncComponent />
template>
<template #fallback>
<div>Loading...div>
template>
Suspense>
template>
在 Vue 3 中,组件可以同时接受多个 v-model,而不仅仅是 value。这意味着你可以为多个 prop 和事件使用不同的 v-model,而不需要再依赖默认的 value 和 input 事件。这对于那些需要多个双向绑定的组件非常有用。
v-model
在 Vue 3 中,组件可以同时接受多个 v-model
,而不仅仅是 value
。这意味着你可以为多个 prop 和事件使用不同的 v-model
,而不需要再依赖默认的 value
和 input
事件。这对于那些需要多个双向绑定的组件非常有用。
例如,假设有一个组件需要接收两个数据:name
和 age
,你可以为它们分别使用不同的 v-model
:
<template>
<MyForm v-model:name="name" v-model:age="age" />
template>
<script>
export default {
data() {
return {
name: 'John',
age: 30,
};
},
};
script>
<template>
<input :value="name" @input="$emit('update:name', $event)" />
<input :value="age" @input="$emit('update:age', $event)" />
template>
<script>
export default {
props: {
name: String,
age: Number,
},
};
script>
在这个例子中,name
和 age
分别使用了两个 v-model
,并且使用了 update:name
和 update:age
事件来进行更新。
Vue 3 引入了自定义 v-model
的能力,允许开发者指定自己的 prop 名和事件名,而不必固定为 value
和 input
。这使得自定义组件的设计更加灵活。
在 Vue 2 中,v-model
默认绑定的是组件的 value
prop,并监听 input
事件,但在 Vue 3 中,你可以通过自定义 prop 和事件名来定义不同的行为。
例如:
<template>
<MyInput v-model="text" />
template>
<script>
export default {
components: {
MyInput: {
props: {
modelValue: String, // 注意这里使用 modelValue 作为 prop
},
emits: ['update:modelValue'], // 自定义事件名
template: '',
},
},
data() {
return {
text: 'Hello Vue 3!',
};
},
};
script>
在这个例子中,MyInput
组件使用了 modelValue
作为 prop,update:modelValue
作为事件名。这让你可以灵活地命名 prop 和事件,而不仅仅局限于 value
和 input
。
Vue 3 还简化了 v-model
的使用方式。你可以使用类似于原生 HTML 的简洁语法,不需要额外指定事件或 prop。
例如,在 Vue 3 中,你可以直接这样写:
这里 Vue 会自动将 v-model
绑定到 modelValue
,并且自动处理 update:modelValue
事件,避免了冗余的配置。
在 Vue 3 中,如果你需要使用不同的事件名称来代替默认的 update:modelValue
,你可以通过 model
选项来进行配置。这在需要更改事件名称的情况下非常有用。
例如,假设你想将事件名改为 changed
,你可以在组件的 model
选项中进行配置:
<template>
<MyInput v-model="text" />
template>
<script>
export default {
components: {
MyInput: {
props: {
modelValue: String,
},
emits: ['changed'],
model: {
prop: 'modelValue',
event: 'changed',
},
template: '',
},
},
data() {
return {
text: 'Hello Vue 3!',
};
},
};
script>
在这个例子中,modelValue
仍然是传入的 prop,但事件名称变成了 changed
,你可以灵活控制事件名。
Vue 3 增强了对自定义渲染器的支持,这意味着你可以用 Vue 来渲染非 DOM 的目标,比如通过 WebGL、Canvas、SVG 或其他渲染系统进行渲染。允许开发者为 Vue 的虚拟 DOM 创建自定义的渲染过程。通过自定义渲染器,开发者可以将 Vue 组件的渲染输出到任何类型的目标(比如原生 DOM、Canvas、WebGL 或者其他平台)上。
createRenderer 是 Vue 3 中的核心 API,用于创建一个自定义的渲染器。它允许你提供自定义的节点操作函数,来替代默认的 DOM 操作。
import { createRenderer } from 'vue';
// 定义一个自定义渲染器
const renderer = createRenderer({
createElement(type) {
// 自定义创建元素的方法
return new MyCustomElement(type); // 创建自定义元素
},
insert(child, parent) {
// 自定义插入元素的方法
parent.appendChild(child);
},
patchProp(el, key, prevValue, nextValue) {
// 自定义属性更新的方法
el.setAttribute(key, nextValue);
},
// 还可以定义更多的渲染逻辑
});
// 使用自定义渲染器来渲染 Vue 组件
const app = createApp(MyComponent);
renderer.render(app, document.getElementById('root')); // 这里的 root 可以是其他非 DOM 的目标
Options API:Vue2使用的API风格,它通过在组件中定义不同的 选项(options)来声明组件的行为,包括数据、方法、生命周期钩子等。
选项式 API 的核心是将一个 Vue 组件的功能分为几个独立的“选项”,这些选项分别代表了组件的不同部分,比如 data
、methods
、computed
、watch
等。
一个典型的 Vue 组件使用选项式 API 的示例如下:
<template>
<div>
<p>{{ message }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
export default {
// 1. data 选项:用于声明组件的响应式数据
data() {
return {
message: 'Hello, Vue 2!',
count: 0,
};
},
// 2. methods 选项:声明组件中的方法
methods: {
increment() {
this.count++;
},
},
// 3. computed 选项:声明计算属性
computed: {
doubledCount() {
return this.count * 2;
},
},
// 4. 生命周期钩子:Vue 提供的一些生命周期回调
mounted() {
console.log('组件已挂载');
},
watch: {
// 5. watch 选项:观察某些数据变化并作出反应
count(newVal, oldVal) {
console.log(`count 改变了:${oldVal} -> ${newVal}`);
},
},
};
</script>
选项式 API 的几个关键部分
data
:声明组件的响应式数据,返回一个对象,包含组件的状态和数据。
methods
:定义组件中的方法,这些方法可以在模板中调用,也可以在生命周期钩子中使用。
computed
:计算属性,它是根据数据计算得出的值,具有缓存功能。当依赖的数据发生变化时,计算属性会自动更新。
watch
:观察属性的变化,可以监听组件数据的变化,并在数据变化时执行某些操作。与计算属性不同,watch
用于处理异步或开销较大的操作。
生命周期钩子:Vue 提供了多个生命周期钩子来让开发者在组件的不同生命周期阶段执行代码。常见的钩子包括 mounted
、created
、beforeDestroy
等。
props
:定义组件的外部输入(即父组件传递给子组件的数据)。通过 props
,你可以在子组件中访问父组件的数据。
emits
(Vue 3 引入):用于声明组件能够触发的自定义事件,通常用于子组件向父组件传递数据。
优点:
data
、methods
、computed
等)都有清晰的分隔,有助于代码的组织。缺点:
data
、methods
、computed
等可能会变得非常庞大,使得代码难以维护和扩展。虽然 Vue 3 引入了 Composition API,但 Vue 3 依然支持选项式 API,开发者可以根据项目的需要选择使用哪种方式。这两者并不是互斥的,开发者可以在同一个项目中混合使用它们。
Composition API:它通过将组件逻辑拆分成函数(而不是像选项式 API 那样按照 data、methods、computed 等进行组织)来提升代码的灵活性、复用性和可维护性。组合式 API 的核心思想是将相关的业务逻辑封装成函数,并在组件中按需引入和使用。这与选项式 API 中将不同功能(如数据、方法、计算属性等)分开组织的方式不同。通过组合式 API,你可以更加自由地管理和复用组件的逻辑。
Vue 3 引入的 组合式 API(Composition API)是一个全新的方式来编写 Vue 组件,它通过将组件逻辑拆分成函数(而不是像选项式 API 那样按照 data
、methods
、computed
等进行组织)来提升代码的灵活性、复用性和可维护性。与选项式 API 相比,组合式 API 提供了一种更加灵活、模块化的方式来组织代码,特别适用于复杂组件的开发。
组合式 API 的常用功能
setup
函数
组合式 API 的入口函数是 setup
。它是 Vue 3 组件中必不可少的部分,所有的响应式数据、计算属性、方法、生命周期钩子等都可以在 setup
中定义。
setup
函数在组件创建之前执行,返回的对象(或函数)会暴露给模板,作为组件的响应式数据和方法。import { ref, reactive } from 'vue';
export default {
setup() {
// 响应式数据
const count = ref(0); // ref 用于基本类型数据
const state = reactive({ name: 'Vue 3' }); // reactive 用于对象或数组
// 计算属性
const doubledCount = computed(() => count.value * 2);
// 方法
const increment = () => {
count.value++;
};
// 返回值暴露给模板
return {
count,
doubledCount,
increment,
state
};
}
};
setup
函数中的返回值会自动暴露给组件的模板,模板中可以直接访问返回的值。setup
中的代码是在 Vue 组件的实例化之前运行的,所以你无法在 setup
中访问 this
。响应式数据(ref
和 reactive
)
ref
用于创建基本数据类型的响应式数据。ref
返回一个包含 .value
属性的对象,只有通过 .value
来访问和修改它的值。
const count = ref(0); // count 是一个响应式数据
count.value++; // 修改时需要使用 .value
reactive
用于创建对象或数组的响应式数据,它会使对象内部的所有属性变成响应式。
const state = reactive({ name: 'Vue 3', age: 3 });
state.name = 'Vue 4'; // state 里的属性是响应式的
ref
和 reactive
是 Vue 3 响应式系统的基础。reactive
更适合用于对象、数组等复杂数据类型,而 ref
更适用于基本数据类型(如 number
、string
、boolean
)。
computed
和 watch
computed
用于定义计算属性,它会根据依赖的响应式数据自动更新。
const count = ref(0);
const doubledCount = computed(() => count.value * 2);
watch
用于监听响应式数据的变化,适用于需要执行副作用操作(如异步操作或外部 API 调用)的场景。
watch(count, (newVal, oldVal) => {
console.log(`count changed from ${oldVal} to ${newVal}`);
});
watch
可以监听单个数据源,也可以监听对象的多个属性。
生命周期钩子
在组合式 API 中,生命周期钩子通过类似于 onMounted
、onCreated
等函数来使用。这些函数可以在 setup
中导入使用。
import { onMounted, onUpdated, onUnmounted } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('组件已挂载');
});
onUpdated(() => {
console.log('组件更新');
});
onUnmounted(() => {
console.log('组件已销毁');
});
}
};
这些生命周期钩子函数在 setup
中使用,可以更加灵活地组织代码。
provide
和 inject
Vue 3 中的 provide
和 inject
机制可以帮助你在组件树的不同层级之间共享数据。provide
在父组件中提供数据,而 inject
让子组件获取这些数据。
provide
:父组件中提供数据。
import { provide } from 'vue';
export default {
setup() {
const sharedData = ref('Hello from parent');
provide('sharedData', sharedData);
}
};
inject
:子组件中获取父组件提供的数据。
import { inject } from 'vue';
export default {
setup() {
const sharedData = inject('sharedData');
console.log(sharedData); // 'Hello from parent'
}
};
优势
逻辑复用:
更好的类型推导:
setup
函数中显式地声明变量的类型,避免一些隐式类型问题。更好的组织和维护:
更清晰的上下文管理:
setup
函数是 Vue 组件的入口,你可以清晰地定义组件的状态和行为,而不会受到其他选项(如 data
、methods
)的干扰。缺点
学习曲线较陡:
代码结构可能不清晰:
模板和 JavaScript 逻辑的分离较弱:
setup
函数暴露给模板,可能导致一些模板和逻辑的边界变得模糊,特别是在大组件中,可能会导致组件难以维护。适用场景:
Vue 3 中的响应式系统引入了 reactive 和 ref:
在 Vue 2 中,响应式对象是通过 Object.defineProperty
实现的,而 Vue 3 使用 Proxy
提供了更高效、更灵活的响应式处理。
当你将一个响应式对象的属性赋值或解构到一个本地变量时,访问或赋值该变量是非响应式的,因为它将不再触发源对象上的 get / set 代理。注意这种“断开”只影响变量绑定——如果变量指向一个对象之类的非原始值,那么对该对象的修改仍然是响应式的。从 reactive() 返回的代理尽管行为上表现得像原始对象,但我们通过使用 === 运算符还是能够比较出它们的不同。
ref:
ref 返回的是一个包含 value 属性的对象,你需要通过 .value
来访问或修改它的值。在模板中,Vue 会自动解包 ref,所以你不需要写 .value。ref 内部是通过 reactive 来实现的。当你调用 ref 时,Vue 会创建一个包含 value 属性的对象,并将这个对象传递给 reactive,使其变成响应式的。
reactive:
reactive 用于创建一个响应式的对象或数组。与 ref 不同,reactive 直接作用于对象或数组,而不需要 .value 来访问或修改。reactive 使用了 Proxy 来实现响应式。Proxy 可以拦截对象的读取、赋值等操作,从而在数据变化时触发视图更新。
与客户端的单页应用 (SPA) 相比,SSR 的优势主要在于:
SSR (Server-Side Rendering) 和 SPA (Single-Page Application) 是两种不同的网页渲染方式。它们的根本区别在于页面内容是在哪一端生成的:是在服务器端还是客户端。下面详细解释这两者的概念、优缺点以及它们之间的区别。
什么是 SSR (Server-Side Rendering)?
SSR(服务器端渲染)是指将应用程序的 HTML 内容在服务器端生成后发送到客户端浏览器。也就是说,当用户请求一个页面时,服务器会根据当前路由渲染完整的 HTML 页面并返回给浏览器,浏览器加载和渲染这个 HTML 页面,然后在客户端进行后续的交互。
SSR 的工作流程:
SSR 的特点:
什么是 SPA (Single-Page Application)?
SPA(单页面应用)是一种通过客户端的 JavaScript 来动态渲染页面的应用。在这种模式下,用户加载页面后,所有的交互、路由跳转、内容更新都通过 JavaScript 来实现,而无需重新加载整个页面。常见的 SPA 框架有 React、Vue、Angular 等。
SPA 的工作流程:
SPA 的特点:
特性 | SSR (服务器端渲染) | SPA (单页面应用) |
---|---|---|
渲染位置 | 页面内容在服务器端渲染后发送到客户端 | 页面内容由客户端通过 JavaScript 动态渲染 |
首屏加载速度 | 首屏加载较快,直接获取完整的 HTML 页面 | 首次加载时较慢,需加载 JavaScript、CSS 和资源 |
交互体验 | 首屏加载后,交互由客户端接管,通常体验流畅 | 不需要重新加载页面,用户交互体验更加流畅 |
SEO 优化 | 由于页面内容在服务器端渲染,搜索引擎可以直接抓取内容 | 由于页面是通过 JavaScript 渲染的,SEO 需要额外配置 |
服务器负担 | 服务器需要每次请求都渲染页面,负担较重 | 服务器仅需提供静态资源,前端通过路由渲染内容,负担较轻 |
客户端路由 | 客户端路由仅在页面加载后激活,通常会有客户端 JavaScript 初始化 | 客户端路由在页面加载后就开始工作,页面内容动态更新 |
复杂度 | 相对复杂,涉及服务器渲染、数据获取、页面更新等多个方面 | 相对简单,前端控制整个应用,逻辑和状态在客户端处理 |
SSR:
SPA:
流畅的用户体验:用户在页面加载后,可以实现无刷新操作,页面之间切换非常流畅,适合动态交互丰富的应用。
较低的服务器负担:服务器只需要提供静态资源,所有的交互都由前端处理,服务器负担较轻。
开发体验好:开发过程中,前后端开发可以独立进行,前端完全控制应用逻辑和状态。
SEO 挑战:由于内容是通过 JavaScript 渲染的,搜索引擎无法直接抓取到页面内容,需要额外配置(例如 SSR、Prerendering)来优化 SEO。
首次加载时间较长:由于需要加载 JavaScript 代码,首次加载页面的时间可能较长,用户看到的内容可能有延迟。
浏览器性能要求高:由于大部分逻辑都在客户端运行,浏览器性能不佳时可能导致应用运行缓慢。
SSR 适用于需要较好 SEO 和更快首屏加载的应用,但其服务器负担较重,开发相对复杂。
SPA 更适合需要动态交互和快速响应的应用,但首次加载较慢,SEO 可能存在问题。
**SEO(Search Engine Optimization,搜索引擎优化)**是一种通过优化网站或网页内容,使其在搜索引擎中获得更高排名的技术和策略。SEO 的目标是提高网站在搜索引擎结果页(SERP)中的可见性,从而吸引更多的自然流量。
静态站点生成 (Static-Site Generation,缩写为 SSG),也被称为预渲染,是另一种流行的构建快速网站的技术。如果用服务端渲染一个页面所需的数据对每个用户来说都是相同的,那么我们可以只渲染一次,提前在构建过程中完成,而不是每次请求进来都重新渲染页面。预渲染的页面生成后作为静态 HTML 文件被服务器托管。
是 Vue 提供的一个内置组件,用于 缓存组件的状态,避免组件在切换过程中重新渲染。它能够在组件切换时,保留组件的状态(包括 DOM 结构、数据等),使得在再次显示该组件时无需重新加载,保持上次的状态,从而提升用户体验和性能。
主要用于包裹动态组件(如v-if
,v-for
,Tab 页切换
和Vue路由
控制的组件)
作用:
缓存组件:通过
,可以缓存已经渲染过的组件,避免每次切换时重新渲染组件,减少不必要的计算和 DOM 操作。
提高性能:当你有多个页面或多个视图组件时,用户在切换视图时不需要重新渲染整个页面,这样能显著减少性能开销。
保持组件状态:缓存组件的状态,例如输入框的内容、滚动位置、组件内部的动画状态等,这些都能保持在组件重新显示时,避免丢失。
KeepAlive 的主要特性
缓存多个组件:
允许多个组件同时被缓存,只要它们在
中渲染过一次,就会保持在内存中。
条件缓存:
可以通过 include 和 exclude 属性,指定哪些组件需要缓存,哪些不需要缓存。
include:一个字符串或正则表达式,用于指定哪些组件应该被缓存。
exclude:一个字符串或正则表达式,用于指定哪些组件不应该被缓存。
<template>
<keep-alive :include="['ComponentA']" :exclude="['ComponentB']">
<component :is="currentComponent">component>
keep-alive>
template>
生命周期钩子: 使用
时,组件的生命周期会有一些不同之处,具体是 activated 和 deactivated 钩子:使用
缓存的组件不会触发 created 或 mounted 等生命周期钩子,而会触发 activated 和 deactivated
是 Vue 3 中引入的一个内置组件,用于 将组件的渲染输出传送到 DOM 树的其他位置。它可以将一个组件的内容渲染到 DOM 中指定的任何位置,而不是在当前组件树中渲染。它主要用于一些场景,比如模态框、弹出层、通知等,需要在文档的不同位置显示,但又需要保持在 Vue 组件的上下文中。确保它们的显示层级不会被父级组件的样式或布局影响。
就是为了解决这个问题,它允许你将组件的 DOM 输出传送到一个完全不同的位置,同时依然保持它在 Vue 的组件树中的状态和逻辑。
组件有两个主要的属性:
to
:指定渲染目标的 DOM 元素。可以是一个选择器(如 #app
)或者是一个 DOM 元素本身。v-if
/ v-show
:可以控制何时渲染内容。<template>
<div>
<button @click="showModal = true">打开模态框button>
<Teleport to="body">
<div v-if="showModal" class="modal">
<div class="modal-content">
<span @click="showModal = false" class="close">×span>
<p>这是一个模态框!p>
div>
div>
Teleport>
div>
template>
<script>
export default {
data() {
return {
showModal: false,
};
},
};
script>
<style>
.modal {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 5px;
text-align: center;
}
.close {
position: absolute;
top: 10px;
right: 10px;
font-size: 20px;
cursor: pointer;
}
style>
在这个例子中,模态框被渲染到 body
元素中,而不是原本父组件的位置。点击按钮会显示模态框,而模态框的 DOM 元素会直接插入到 body
中,而不是嵌套在组件的 DOM 树内。
<template>
<div>
<button @click="toggleMessage">切换消息显示button>
<Teleport to="#notification-area">
<div v-if="showMessage" class="notification">
这是一个通知消息!
div>
Teleport>
<div id="notification-area">div>
div>
template>
<script>
export default {
data() {
return {
showMessage: false,
};
},
methods: {
toggleMessage() {
this.showMessage = !this.showMessage;
}
}
};
script>
<style>
.notification {
background-color: #f0ad4e;
color: white;
padding: 10px;
border-radius: 5px;
margin-top: 10px;
}
style>
在这个例子中,通知消息会被传送到页面上一个名为 #notification-area
的区域,而不是放在当前组件的位置。每次点击按钮会在该区域显示或隐藏通知。
的工作原理
渲染目标:
会通过其 to
属性指定一个 DOM 目标位置,组件的渲染内容会被插入到这个目标位置中。例如,可以是 body
、#id
或者传入的 DOM 元素。
保持响应式:
尽管组件的 DOM 被移动到了文档的其他位置,但它仍然保留 Vue 的响应式机制,保持组件的状态、事件处理、绑定等特性。所以,不管这个内容被渲染到哪里,数据和事件仍然按照组件的逻辑工作。
生命周期:
通过
渲染的内容依旧保持原本组件的生命周期钩子。例如,它的 mounted
、updated
、destroyed
等生命周期钩子会在其正常生命周期中触发。
v-if 与 Teleport:
如果你使用 v-if
控制
渲染的内容,当该内容被移除时,它会被从目标 DOM 移除,并且销毁相应的 Vue 组件实例。
保持 Vue 响应式:尽管组件的 DOM 被移动到了其他地方,Vue 仍然会保持其响应式特性,数据、事件等都能正常工作。
Vue 生命周期钩子:由于组件的 DOM 被插入到不同的地方,mounted
钩子会在目标位置的插入时触发,而 destroyed
会在移除时触发。
CSS 样式:由于被传送到页面其他地方,某些组件的样式可能会受到影响。确保组件本身的样式可以独立或外部样式不会干扰其显示。
在 Vue 3 中,Proxy 是核心特性之一,用于实现 响应式数据(reactivity)系统。Vue 3 使用 JavaScript 的 Proxy
API 来替代 Vue 2 中的 Object.defineProperty
来进行数据代理。这使得 Vue 3 在响应式性能和易用性上都有了显著的提升。
Proxy
是 JavaScript 的一种新特性(在 ES6 中引入),它可以用来定义一个对象的代理。通过 Proxy
,你可以拦截对对象的操作(如属性访问、修改、删除等),并定义自定义的行为。Proxy
的语法如下:
let proxy = new Proxy(target, handler);
target
:需要被代理的目标对象。handler
:一个对象,用于定义拦截操作(例如 get
、set
、deleteProperty
等)。在 Vue 3 中,响应式系统就是基于 Proxy
实现的。Vue 会通过 Proxy
来代理数据对象,捕获对数据的各种操作(如读取、修改、删除、赋值等),并自动更新视图。当数据发生变化时,Vue 会触发视图的更新,从而实现响应式。
Vue 3 Proxy 的主要功能
拦截属性访问(get
):
当访问某个属性时,Proxy
会捕获到这个操作,并执行 get
钩子。Vue 会记录依赖(即哪些组件或计算属性依赖于这个属性)。
拦截属性赋值(set
):
当修改对象的某个属性时,Proxy
会捕获这个赋值操作,并执行 set
钩子。Vue 会在 set
钩子中触发视图更新。
拦截属性删除(deleteProperty
):
当删除某个属性时,Proxy
会捕获这个操作,Vue 会处理相关的依赖更新。
拦截对象的其他操作:
比如操作对象的长度、遍历对象(for...in
)等,Vue 会通过 Proxy
捕获并处理。
Vue 3 Proxy 工作原理
代理数据对象:
Vue 3 在初始化数据时,会将数据对象通过 Proxy
进行代理。代理后的对象会拦截对数据对象的访问,监控每个属性的变化。
依赖收集:
当组件访问某个数据属性时,Vue 会记录这个访问行为,标记该属性为该组件的依赖。这样,当数据发生变化时,组件就会重新渲染。
触发视图更新:
当数据属性被修改时,set
操作会被触发,Vue 会通过 Proxy
捕获到这个变化,并通知视图更新。它会通知所有依赖于这个数据的组件或计算属性,进行重新渲染。
Proxy
与 Vue 2 的响应式系统对比
在 Vue 2 中,响应式系统是通过 Object.defineProperty
实现的。Object.defineProperty
会为每个属性定义 getter 和 setter,从而拦截对该属性的访问和修改。这种方式的缺点包括:
而 Vue 3 使用 Proxy
解决了这些问题,Proxy
具有以下优势:
Proxy
可以直接代理整个对象或数组,不需要为每个属性单独定义 getter 和 setter。Proxy
,你可以直接添加或删除属性,Vue 会自动处理这些操作。Proxy
的性能比 Object.defineProperty
更好,尤其在大量数据更新的情况下。Vue 3 中 Proxy
代理的数据对象的特点
reactive
函数将对象转换为响应式对象后,这个对象会被 Proxy
代理,所有对它的操作都会被拦截和处理。Proxy
是基于引用的,意味着通过代理对象修改数据会影响到原始对象。Proxy
支持深度嵌套。当你访问嵌套对象时,Vue 会为嵌套对象中的每个属性都创建一个 Proxy
代理。const state = reactive({
user: {
name: "John",
age: 30
}
});
// 修改嵌套对象属性
state.user.name = "Jane"; // 会触发 Proxy 的 `set` 方法
在这个例子中,state.user
也是一个 Proxy
代理的对象。因此,当你修改 state.user.name
时,它会触发嵌套的 Proxy
的 set
操作。
Vue 3 Proxy 的性能优化:
Proxy
代理。这样可以避免不必要的代理操作。