前端--深入了解Vue3
Vue 3 是一个现代化的前端框架,基于组件化的架构构建用户界面。它的核心原理围绕 响应式系统、虚拟 DOM 和 组件化设计 展开。以下是 Vue 3 核心原理的详细描述
1. 响应式系统
Vue 3 的响应式系统是其核心部分之一,通过数据和视图之间的绑定,自动管理 UI 更新。
1.1. Proxy 机制
Vue 3 引入了 JavaScript 的Proxy对象来实现响应式的核心。每当对象中的属性被读取、修改或删除时,Proxy可以捕获这些操作,并自动更新相关的视图。
代码示例:
let target = {};
let handler = {
get: function(target, prop, receiver) {
console.log(`Getting ${prop}!`);
return Reflect.get(target, prop, receiver);
},
set: function(target, prop, value, receiver) {
console.log(`Setting ${prop}!`);
return Reflect.set(target, prop, value, receiver);
}
};
let proxy = new Proxy(target, handler);
proxy.name = "John"; // 输出:Setting name!
console.log(proxy.name); // 输出:Getting name! John
1.2. 依赖追踪
Vue 3 通过追踪组件依赖的状态来优化视图的更新。只会重新渲染那些真正依赖数据发生改变的部分,从而减少不必要的更新。
1.3. 计算属性与侦听器
通过 computed
和 watch
,开发者可以创建依赖于其他状态的派生数据或观察特定数据的变化。computed
是基于依赖自动缓存的,而 watch
则是响应数据的变化执行特定逻辑。
computed
是通过effect
函数结合 Vue 的响应式系统实现的。每次computed
依赖的数据发生改变,Vue 会标记它为“脏数据”,并在下次访问时重新计算。
示例代码:
// 假设有一个响应式系统
let activeEffect; // 当前的 effect,用于依赖追踪
// 依赖追踪器
class Dep {
constructor() {
this.subscribers = new Set();
}
// 依赖收集
depend() {
if (activeEffect) {
this.subscribers.add(activeEffect);
}
}
// 通知所有依赖
notify() {
this.subscribers.forEach(effect => effect());
}
}
// 实现响应式数据
function reactive(target) {
const depsMap = new WeakMap(); // 用来存储每个属性的依赖
return new Proxy(target, {
get(obj, key) {
let dep = depsMap.get(key);
if (!dep) {
dep = new Dep();
depsMap.set(key, dep);
}
dep.depend(); // 依赖收集
return obj[key];
},
set(obj, key, value) {
obj[key] = value;
const dep = depsMap.get(key);
if (dep) {
dep.notify(); // 依赖更新时通知
}
return true;
}
});
}
// 实现 effect 函数,用于追踪依赖
function effect(fn) {
activeEffect = fn;
fn(); // 立即执行一次,收集依赖
activeEffect = null;
}
// 简单的 computed 实现
function computed(getter) {
let cachedValue;
let isDirty = true;
// 创建一个依赖于 getter 的 effect
const runner = effect(() => {
if (isDirty) {
cachedValue = getter();
isDirty = false;
}
});
return {
get value() {
if (isDirty) {
runner(); // 如果数据是脏的,重新运行计算
}
return cachedValue;
},
set value(newValue) {
console.warn('Cannot set value for a computed property');
}
};
}
// 示例:
// 定义响应式数据
const state = reactive({ num: 2 });
// 定义计算属性
const doubleNum = computed(() => {
console.log('计算 doubleNum');
return state.num * 2;
});
// 依赖追踪与重新计算
console.log(doubleNum.value); // 输出:计算 doubleNum, 4
state.num = 3; // 更新 num
console.log(doubleNum.value); // 输出:计算 doubleNum, 6
watch
是通过 Vue 3 的响应式系统中的依赖追踪机制实现的。它会观察指定的响应式数据,并在数据变化时触发回调函数。
示例代码:
// 简单的 watch 实现
function watch(source, callback) {
// 创建一个依赖于 source 的 effect
let oldValue;
const runner = effect(() => {
const newValue = source();
if (newValue !== oldValue) {
callback(newValue, oldValue); // 触发回调
oldValue = newValue; // 更新旧值
}
});
}
// 示例:
// 定义响应式数据
const state = reactive({ num: 0 });
// 定义 watch 监听器
watch(() => state.num, (newVal, oldVal) => {
console.log(`num 变化了: 从 ${oldVal} 到 ${newVal}`);
});
// 修改 num 值,会触发 watch 回调
state.num = 5; // 输出:num 变化了: 从 0 到 5
state.num = 10; // 输出:num 变化了: 从 5 到 10
2. 虚拟 DOM
Vue 3 使用 虚拟 DOM (Virtual DOM) 来提高性能。虚拟 DOM 是一个 JavaScript 对象的树结构,代表 UI 的状态。每次状态发生变化时,Vue 3 会创建一个新的虚拟 DOM,并将其与旧的虚拟 DOM 进行对比,使用 diff 算法 找出发生变化的部分,最终只更新需要变动的部分。
2.1. Diff 算法
Vue 3 中的虚拟 DOM diff 算法通过逐层比较新旧虚拟 DOM 节点来找出变化点,从而进行高效的最小化 DOM 更新。
2.2. 模板与渲染函数
Vue 3 通过模板引擎生成虚拟 DOM 结构,开发者也可以手写渲染函数来直接控制虚拟 DOM 的生成。Vue 的模板语法最终会被编译成渲染函数。
3. 组合式 API(Composition API)
Vue 3 提供了新的 组合式 API,允许开发者以更加灵活和可复用的方式组织组件逻辑。它是 Vue 3 的一个重要改进,相比于 Vue 2 的选项式 API 更加适合大型应用的开发。
3.1.setup函数
setup 是组合式 API 的核心,在组件实例创建之前调用。使用setup后,不用写setup函数;组件只需要引入不需要注册;属性和方法也不需要再返回,可以直接在template模板中使用。
代码示例:
<template>
<my-component @click="func" :numb="numb"></my-component>
</template>
<script lang="ts" setup>
import {ref} from 'vue';
import myComponent from '@/component/myComponent.vue';
//此时注册的变量或方法可以直接在template中使用而不需要导出
const numb = ref(0);
let func = ()=>{
numb.value++;
}
</script>
新增的api:
- defineProps:子组件接收父组件中传来的props
- defineEmits:子组件调用父组件中的方法
- defineExpose:子组件暴露属性,可以在父组件中拿到
3.2. Reactivity API
包括ref、reactive、computed、watch等函数,允许开发者创建响应式的状态和计算属性。
- ref用来处理基本数据类型的响应式(number、String、boolean、null、对象或数组),需要通过.value访问数据的内部值
代码示例:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">增加</button>
</div>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
// 使用 ref 创建响应式数据,适用于单一数值或原始类型
const count = ref(0); // 包装了一个 number 类型
const increment = () => {
count.value++; // 注意需要使用 .value 来访问和更新值
};
return {
count,
increment
};
}
};
</script>
- reactive用于处理复杂的对象类型和嵌套结构的响应式(如Vuex中的store状态),不需要通过.value访问值
代码示例:
<template>
<div>
<p>User Info:</p>
<p>Name: {{ user.name }}</p>
<p>Age: {{ user.age }}</p>
<button @click="updateUser">更新用户</button>
</div>
</template>
<script>
import { reactive } from 'vue';
export default {
setup() {
// 使用 reactive 创建复杂对象的响应式数据
const user = reactive({
name: 'John',
age: 25
});
const updateUser = () => {
user.name = 'Jane'; // 直接修改属性,不需要 .value
user.age = 30;
};
return {
user,
updateUser
};
}
};
</script>
4. 组件化设计
Vue 3 是基于 组件 的前端框架。组件是 Vue 应用的基本构建单元,每个组件封装了其自身的模板、逻辑和样式。
4.1. 单文件组件 (SFC)
Vue 3 使用 .vue
文件作为单文件组件,将模板、逻辑和样式组合在一个文件中,这使得组件更加易于维护和重用。单文件组件可以编译成原生 JavaScript 代码和 CSS。
4.1. Props 和事件
组件之间通过 props传递数据(props是单向数据流,父组件的数据会流向子组件,但是子组件无法修改),并通过事件实现父子组件之间的通信。Vue 3 中支持emit事件进行父组件和子组件之间的互动。
代码示例:
<template>
<div>
<h2>父组件</h2>
<p>父组件的数据: {{ parentMessage }}</p>
<!-- 通过 props 传递数据,并监听子组件触发的事件 -->
<ChildComponent :message="parentMessage" @update-message="handleUpdateMessage" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
data() {
return {
parentMessage: '这是来自父组件的数据'
};
},
methods: {
// 父组件处理子组件事件的方法
handleUpdateMessage(newMessage) {
this.parentMessage = newMessage;
}
}
};
</script>
<template>
<div>
<h3>子组件</h3>
<p>接收到父组件的数据: {{ message }}</p>
<!-- 触发事件,通知父组件 -->
<button @click="updateMessage">更新父组件数据</button>
</div>
</template>
<script>
export default {
props: {
message: {
type: String,
required: true
}
},
methods: {
updateMessage() {
// 触发自定义事件,并传递新的消息给父组件
this.$emit('update-message', '这是来自子组件的更新数据');
}
}
};
</script>
4.1. 插槽 (Slots):Vue 通过插槽机制允许开发者在组件中插入动态内容,支持具名插槽和作用域插槽来提供更强的灵活性。
代码示例:
<template #createTime="{ record }">
{{ moment(record.createTime).format("YYYY-MM-DD") }}
</template>
//script数据定义
const columns = [
{
title: "题目名称",
dataIndex: "title",
},
{
title: "创建时间",
slotName: "createTime",
},
];
5. 模板编译与优化
Vue 3 的模板语法非常直观,它会将模板编译为虚拟 DOM 渲染函数。
5.1. 编译优化
Vue 3 的编译器针对不变的节点进行了静态提升,这意味着在渲染过程中不需要重新创建不变的虚拟 DOM,从而减少了不必要的开销。
5.2. 基于静态分析的优化
Vue 3 能够通过编译期的静态分析确定哪些节点是动态的,哪些是静态的,进而只追踪动态内容,进一步提升了渲染性能。
6. Fragment 和 Teleport
6.1. Fragment
Vue 3 的组件不再要求只能有一个根节点,允许返回多个根元素,这使得开发者编写组件时更加自由。
6.2. Teleport
允许开发者将组件的 DOM 渲染到另一个 DOM 位置,而不必局限于当前组件的作用域内。
7. 性能改进
7.1. Tree-shaking
Vue 3 采用模块化设计,可以通过 tree-shaking 移除未使用的代码模块,减少打包后的文件大小。
7.2. 内存优化
通过使用 Proxy
替代 Vue 2 的 Object.defineProperty
来实现响应式系统,减少了内存占用并提高了对大型数据集的处理效率。
8. 生态与工具链
8.1. Vue CLI 和 Vite
Vue 提供了强大的 CLI 工具和快速的构建工具 Vite 来简化项目的创建和开发环境的搭建。
8.2. Vue Router 和 Vuex
Vue 3 生态中包含 Vue Router(路由管理)和 Vuex(状态管理),提供了完整的解决方案来构建大型单页应用。
Vue Router示例代码:
export const routes: Array<RouteRecordRaw> = [
{
path: "/user",
name: "用户",
component: UserLayout,
children: [
{
path: "/user/login",
name: "用户登录",
component: UserLoginView,
},
{
path: "/user/register",
name: "用户注册",
component: UserRegisterView,
},
],
meta: {
hideInMenu: true,
},
},
];
Vuex示例代码:
export default {
namespaced: true,
state: () => ({
loginUser: {
userName: "xuwei",
},
}),
actions: {
async getLoginUser({ commit, state }, payload) {
// 从远程请求获取登录信息
const res = await UserControllerService.getLoginUserUsingGet();
if (res.code === 0) {
commit("updateUser", res.data);
} else {
commit("updateUser", {
...state.loginUser,
userRole: ACCESS_ENUM.NOT_LOGIN,
});
}
},
},
mutations: {
updateUser(state, payload) {
state.loginUser = payload;
},
},
} as StoreOptions<any>;