响应式系统
Vue 2
- 技术基础 :使用
Object.defineProperty
实现响应式。 - 局限性 :
- 无法监听新增属性:如果在创建实例后添加新属性,这些属性不会自动成为响应式的。
- 数组变更检测问题:直接通过索引设置值或长度不会触发更新。
示例:Vue 2 中的响应式数据
javascript
// Vue 2 示例
new Vue({
data() {
return {
message: 'Hello, Vue 2!',
items: []
};
},
methods: {
addItem() {
// 直接修改数组不会触发视图更新
this.items[0] = 'New Item';
// 正确的做法是使用 Vue.set 或 this.$set
this.$set(this.items, 1, 'Another Item');
}
}
});
Vue 3
- 技术基础 :基于 ES6
Proxy
的全新响应式系统。 - 优势 :
- 全面监听:可以拦截对象的所有操作,包括属性的添加、删除、访问和修改。
- 深层嵌套对象:对深层嵌套的对象也能自动追踪变化。
示例:Vue 3 中的响应式数据
javascript
// Vue 3 示例
import { reactive } from 'vue';
const state = reactive({
message: 'Hello, Vue 3!',
items: []
});
state.items.push('New Item'); // 自动触发视图更新
state.items[0] = 'Updated Item'; // 自动触发视图更新
组合式 API (Composition API)
Vue 2
- 逻辑复用:依赖于混入(mixins),容易导致命名冲突和维护困难。
Vue 3
- 逻辑复用:引入了 Composition API,允许开发者将相关逻辑封装到函数中,并在不同的组件之间共享。
示例:组合式 API 使用
javascript
// 定义一个可重用的逻辑钩子
import { ref, onMounted } from 'vue';
function useMousePosition() {
const x = ref(0);
const y = ref(0);
function update(event) {
x.value = event.pageX;
y.value = event.pageY;
}
onMounted(() => window.addEventListener('mousemove', update));
return { x, y };
}
// 在组件中使用
export default {
setup() {
const { x, y } = useMousePosition();
return {
x,
y
};
}
};
生命周期钩子
Vue 2
- 钩子名称 :提供了明确的生命周期钩子,如
beforeCreate
,created
,beforeMount
,mounted
,beforeUpdate
,updated
,beforeDestroy
, 和destroyed
。
Vue 3
-
钩子调整 :保留了大部分原有的钩子,但为了统一命名规则,做了如下更改:
-
创建阶段:setup
-
挂载阶段:onBeforeMount,onMounted
-
更新阶段:onBeforeUpdate,onUpdated
-
卸载阶段:onBeforeUnmount,onUnmounted
-
-
setup()
钩子:这是一个新的组件选项,运行在实例创建之前。
示例:Vue 3 中使用生命周期钩子
javascript
import { onMounted, onUnmounted } from 'vue';
export default {
setup() {
onMounted(() => {
console.log('Component is mounted');
});
onUnmounted(() => {
console.log('Component is unmounted');
});
return {};
}
};
模板支持与渲染函数
Vue 2
- 根节点限制:模板必须有一个单一的根元素。
Vue 3
- 片段支持:移除了单个根节点的要求,允许模板中有多个顶级元素。
示例:Vue 3 中的多根节点模板
javascript
<template>
<h1>Title</h1>
<p>Paragraph</p>
</template>
性能提升
Vue 2
- 虚拟 DOM:尽管已经很高效,但在大规模应用中仍可能遇到性能瓶颈。
Vue 3
- 改进的虚拟 DOM:重新设计的虚拟 DOM 实现,采用了更高效的算法来比较和更新 DOM 树。
示例:Vue 3 中的性能优化
虽然性能优化更多体现在框架内部,但从开发者的角度来看,减少不必要的计算和渲染可以帮助提高性能。例如,使用 computed
属性而不是方法来缓存计算结果:
javascript
import { computed } from 'vue';
export default {
setup() {
const count = ref(0);
const doubleCount = computed(() => count.value * 2);
return {
count,
doubleCount
};
}
};
TypeScript 支持
Vue 2
- 类型支持:虽然可以结合 TypeScript 使用,但不是从一开始就设计好的。
Vue 3
- 全面支持:核心库完全用 TypeScript 编写,提供了强大的类型定义和支持。
示例:Vue 3 中使用 TypeScript
javascript
import { defineComponent, ref } from 'vue';
export default defineComponent({
name: 'MyComponent',
props: {
msg: {
type: String as PropType<string>,
required: true
}
},
setup(props) {
const count = ref<number>(0);
return {
count,
increment: () => count.value++
};
}
});
包大小
Vue 2
- 体积问题:随着功能的增加,Vue 2 的核心库体积也在增大。
Vue 3
- 模块化设计:通过 tree-shaking 技术,Vue 3 能够只打包实际使用的代码,显著减小了最终打包文件的体积。
示例:Vue 3 中的按需加载
javascript
// 只导入需要的部分
import { createApp, ref } from 'vue';
组件注册与全局配置
Vue 2
- 组件注册 :可以使用
Vue.component
注册全局组件,或者在单文件组件中通过components
选项注册局部组件。 - 全局配置 :如全局事件总线、全局过滤器等,通常通过
Vue.prototype
或者Vue.mixin
实现。
Vue 3
- 组件注册 :推荐使用
app.component
来注册全局组件,局部组件仍然可以通过components
选项注册。 - 全局配置 :不再推荐使用
Vue.prototype
或Vue.mixin
,而是提供了新的 API 如app.config.globalProperties
和provide/inject
来实现类似功能。
示例:Vue 3 中的全局属性
javascript
import { createApp } from 'vue';
import App from './App.vue';
const app = createApp(App);
// 添加全局属性或方法
app.config.globalProperties.$globalMethod = () => {
console.log('This is a global method');
};
app.mount('#app');
插件系统
Vue 2
- 插件安装 :通过
Vue.use(plugin)
安装插件,插件可以添加全局组件、指令、混入等。
Vue 3
- 插件安装 :依然使用
app.use(plugin)
安装插件,但插件的设计更加模块化,鼓励按需加载和更细粒度的功能分割。
示例:Vue 3 中的插件
javascript
// 插件定义
export default {
install(app, options) {
// 添加一个全局方法
app.config.globalProperties.$myPluginMethod = function() {
console.log('Plugin method called');
};
// 添加一个全局指令
app.directive('focus', {
mounted(el) {
el.focus();
}
});
}
};
// 使用插件
import myPlugin from './plugins/myPlugin';
const app = createApp(App);
app.use(myPlugin);
app.mount('#app');
指令系统
Vue 2
- 自定义指令 :通过
Vue.directive
或者在组件内部使用directives
选项来定义自定义指令。
Vue 3
- 自定义指令 :仍然支持自定义指令,但 API 略有变化,现在是通过
app.directive
来定义。此外,钩子函数的名称也做了调整(例如,bind
改为mounted
)。
示例:Vue 3 中的自定义指令
javascript
import { createApp } from 'vue';
const app = createApp({});
app.directive('colorize', {
mounted(el, binding) {
el.style.color = binding.value || 'red'; // 默认颜色为红色
}
});
app.mount('#app');
// 在模板中使用
<template>
<p v-colorize="'blue'">这段文本应该是蓝色。</p>
</template>
自定义渲染器
Vue 2
- 自定义渲染器:如果需要创建自定义渲染器(例如用于不同的 DOM 环境),则需要从头开始编写,这相对复杂。
Vue 3
- 自定义渲染器 :引入了
@vue/runtime-core
和@vue/runtime-dom
,使得创建自定义渲染器变得更加简单和模块化。开发者可以利用这些包提供的工具函数快速搭建适合特定环境的渲染逻辑。
示例:Vue 3 中的自定义渲染器
虽然这个例子较为复杂,但它展示了如何创建一个简单的自定义渲染器:
javascript
import { createRenderer } from '@vue/runtime-core';
const renderer = createRenderer({
createElement(type) {
return document.createElement(type);
},
patchProp(el, key, prevValue, nextValue) {
if (key.startsWith('on')) {
const event = key.slice(2).toLowerCase();
el.addEventListener(event, nextValue);
} else {
if (nextValue == null) {
el.removeAttribute(key);
} else {
el.setAttribute(key, nextValue);
}
}
},
insert(child, parent, anchor = null) {
parent.insertBefore(child, anchor);
},
remove(child) {
const parent = child.parentNode;
if (parent) {
parent.removeChild(child);
}
},
setElementText(node, text) {
node.textContent = text;
}
});
renderer.createApp(App).mount(document.querySelector('#app'));
响应式数据类型
Vue 2
- 响应式数据类型:仅限于对象和数组。
Vue 3
- 响应式数据类型 :除了对象和数组外,还支持原始类型的响应式包装(如
ref
和reactive
)。这允许对基本类型(如数字、字符串)进行响应式处理。
示例:Vue 3 中的 ref
和 reactive
javascript
import { ref, reactive } from 'vue';
// 使用 ref 包装基本类型
const count = ref(0);
// 使用 reactive 包装对象
const state = reactive({
message: 'Hello, Vue!',
items: []
});
// 更新响应式数据
count.value++;
state.items.push('New Item');
错误处理与调试
Vue 2
- 错误处理:主要依赖于开发者手动捕获错误并在代码中处理,Vue Devtools 提供了一些基本的帮助。
Vue 3
- 错误处理:增强了内置的错误处理机制,提供了更友好的错误捕获和报告方式。同时,Vue Devtools 也得到了显著改进,增加了更多调试选项,如时间旅行调试、性能分析等。
示例:Vue 3 中的错误处理
javascript
import { createApp } from 'vue';
const app = createApp(App);
// 设置全局错误处理器
app.config.errorHandler = (err, vm, info) => {
console.error(`Error in ${info}: ${err.message}`);
};
app.mount('#app');
插槽(Slots)
Vue 2
- 语法多样:既有具名插槽也有作用域插槽,存在多种语法形式。
Vue 3
- 统一语法 :
v-slot
成为了指定作用域插槽的唯一方式。
示例:Vue 3 中的作用域插槽
javascript
<!-- 父组件 -->
<template>
<child-component v-slot="slotProps">
{{ slotProps.message }}
</child-component>
</template>
<!-- 子组件 -->
<template>
<slot :message="msg"></slot>
</template>
<script>
export default {
data() {
return {
msg: 'Hello from child'
};
}
};
</script>
新增功能
Vue 3
- Teleport:用于解决弹出层等场景下的 DOM 结构问题。
- Suspense:用于等待异步依赖加载完成。
示例:使用 Teleport 和 Suspense
javascript
<!-- 使用 Teleport 将内容移动到 body -->
<template>
<teleport to="body">
<div id="modal">Modal content</div>
</teleport>
</template>
<script>
import { defineAsyncComponent } from 'vue';
export default {
components: {
AsyncComponent: defineAsyncComponent(() => import('./components/AsyncComponent.vue'))
}
};
</script>
<!-- 使用 Suspense 等待异步组件加载 -->
<template>
<suspense>
<template #default>
<async-component />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</suspense>
</template>
移除的功能
Vue 3
.native
修饰符:移除,现在可以直接在自定义组件上监听原生事件。- keyCode 事件修饰符:移除,推荐使用键盘事件对象进行键码判断。
- 过滤器:移除,鼓励使用计算属性或其他替代方案。
示例:Vue 3 中处理键盘事件
javascript
<template>
<input @keyup.enter="submitForm" />
</template>
<script>
export default {
methods: {
submitForm(event) {
if (event.key === 'Enter') {
// 处理表单提交逻辑
}
}
}
};
</script>
其他改进
Vue 3
- 调试工具:Vue Devtools 得到了增强,提供了更多调试选项。
- SSR 支持:改善了服务器端渲染的支持。
- 错误处理:提供了更友好的错误捕获和报告机制。
示例:Vue 3 中的全局错误处理
javascript
import { createApp } from 'vue';
const app = createApp(App);
app.config.errorHandler = (err, vm, info) => {
// 处理错误
console.error(`Error in ${info}: ${err.message}`);
};
app.mount('#app');