Vue笔记(最新)
Vue笔记(最新)
一、背景与问题
在现代前端开发中,Vue.js 已成为主流框架之一。其核心优势在于响应式数据绑定和组件化开发模式,但随着项目规模扩大,开发者常面临以下问题:
- 响应性失效:手动修改数组时无法触发视图更新
- 组件通信复杂:父子组件通信、兄弟组件通信、跨层级通信的解决方案选择
- 性能瓶颈:大型项目中频繁的 DOM 更新导致性能下降
- 状态管理混乱:多组件共享状态时容易产生数据耦合
本文将深入解析 Vue 的核心机制,结合真实开发场景,给出可复用的解决方案。
二、基本原理
1. 响应式系统原理
Vue 通过 Proxy 实现响应式系统(Vue 3)或 Object.defineProperty(Vue 2),核心原理如下:
// Vue 3 响应式系统核心代码
function createReactive(obj) {
return new Proxy(obj, {
get(target, key) {
return Reflect.get(target, key);
},
set(target, key, value) {
const oldValue = target[key];
const newValue = value;
// 触发更新逻辑
return Reflect.set(target, key, value);
}
});
}关键点:
Proxy可拦截所有属性访问和修改- Vue 3 使用
Reflect保持兼容性 - 通过
Dep依赖收集和Watcher观察者模式实现响应式更新
2. 虚拟 DOM 工作机制
Vue 的虚拟 DOM 采用 diff 算法实现高效更新:
function diff(oldVNode, newVNode) {
// 1. 全等匹配直接返回
if (oldVNode === newVNode) return;
// 2. 类型不同直接替换
if (oldVNode.tagName !== newVNode.tagName) {
return createNewVNode(newVNode);
}
// 3. 属性更新
if (oldVNode.attrs !== newVNode.attrs) {
updateAttrs(oldVNode, newVNode);
}
// 4. 子节点递归比较
if (oldVNode.children !== newVNode.children) {
diffChildren(oldVNode, newVNode);
}
}性能优化点:
- 只更新变化的部分
- 使用
key属性优化列表更新 - 通过
v-once防止重复渲染
3. 组件系统设计
Vue 的组件系统通过 defineProps/defineEmits 实现:
<script setup>
const props = defineProps({
message: {
type: String,
required: true
}
});
const emit = defineEmits(['update']);
function handleUpdate() {
emit('update', 'New message');
}
</script>
<template>
<div @click="handleUpdate">{{ props.message }}</div>
</template>核心机制:
- props 通过
Proxy实现响应式 - events 通过
event bus传递 - 组件实例通过
vnode管理生命周期
三、环境准备
# 安装 Vue CLI
npm install -g @vue/cli
# 创建新项目
vue create vue-project
# 进入项目目录
cd vue-project
# 安装依赖
npm install建议项目结构:
src/
├── components/ # 组件目录
├── views/ # 页面目录
├── stores/ # 状态管理
├── services/ # 业务逻辑
├── utils/ # 工具函数
├── App.vue # 根组件
└── main.js # 入口文件四、核心实现
1. 响应式数据绑定示例
<template>
<div>
<p>当前计数:{{ count }}</p>
<button @click="increment">+1</button>
<button @click="reset">重置</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
function increment() {
count.value++;
}
function reset() {
count.value = 0;
}
</script>关键代码解释:
ref创建响应式变量count.value是访问值的唯一方式- 修改
value会触发视图更新
2. 组件通信示例
<!-- ParentComponent.vue -->
<template>
<ChildComponent :message="parentMessage" @update="handleUpdate" />
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
const parentMessage = ref('Hello from parent');
const handleUpdate = (newMessage) => {
parentMessage.value = newMessage;
};
</script><!-- ChildComponent.vue -->
<template>
<div>
<p>{{ message }}</p>
<button @click="updateMessage">更新消息</button>
</div>
</template>
<script setup>
import { defineProps, defineEmits } from 'vue';
const props = defineProps({
message: {
type: String,
required: true
}
});
const emit = defineEmits(['update']);
function updateMessage() {
const newMessage = 'Hello from child';
emit('update', newMessage);
}
</script>通信机制:
- 父组件通过
props传递数据 - 子组件通过
emit触发事件 - 事件处理函数在父组件中定义
3. 路由导航示例
// main.js
import { createApp } from 'vue';
import { createRouter, createWebHistory } from 'vue-router';
import App from './App.vue';
import Home from './views/Home.vue';
import About from './views/About.vue';
const routes = [
{ path: '/', component: Home },
{ path: '/about', component: About }
];
const router = createRouter({
history: createWebHistory(),
routes
});
createApp(App).use(router).mount('#app');<!-- App.vue -->
<template>
<router-view />
</template>路由机制:
- 使用
createRouter创建路由实例 createWebHistory支持 HTML5 历史模式router-view动态渲染匹配的组件
五、完整案例
待办事项管理应用
项目结构:
src/
├── components/
│ └── TodoList.vue
├── views/
│ ├── Home.vue
│ └── Settings.vue
├── stores/
│ └── todos.js
├── services/
│ └── api.js
├── App.vue
└── main.js核心代码:
<!-- src/views/Home.vue -->
<template>
<div>
<TodoList :todos="todos" @delete="deleteTodo" />
<div>
<input v-model="newTodo" placeholder="输入新任务" />
<button @click="addTodo">添加</button>
</div>
</div>
</template>
<script setup>
import { ref } from 'vue';
import TodoList from '../components/TodoList.vue';
import { useTodosStore } from '../stores/todos';
const todos = useTodosStore();
const newTodo = ref('');
function addTodo() {
if (newTodo.value.trim()) {
todos.addTodo(newTodo.value);
newTodo.value = '';
}
}
function deleteTodo(id) {
todos.deleteTodo(id);
}
</script>// src/stores/todos.js
import { ref } from 'vue';
export const useTodosStore = () => {
const todos = ref([
{ id: 1, text: '学习 Vue' },
{ id: 2, text: '完成项目' }
]);
function addTodo(text) {
todos.value.push({
id: Date.now(),
text
});
}
function deleteTodo(id) {
todos.value = todos.value.filter(todo => todo.id !== id);
}
return { todos, addTodo, deleteTodo };
};关键点:
- 使用
ref管理状态 - 组件间通过 store 共享状态
- 通过
@delete事件触发删除操作
六、源码解析
1. Vue 3 响应式系统源码
// src/core/observer/index.js
export function createReactive(obj) {
return new Proxy(obj, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
const oldValue = target[key];
const newValue = value;
// 触发更新
trigger(target, key, oldValue, newValue);
return Reflect.set(target, key, value, receiver);
}
});
}关键函数:
track:收集依赖(Dep)trigger:触发更新(Watcher)Dep类管理依赖关系
2. 虚拟 DOM 构建过程
// src/core/vdom/createVNode.js
function createVNode(type, props, children) {
const vnode = {
type,
props,
children,
key: props?.key,
// 其他属性
};
// 创建组件实例
if (typeof type === 'function') {
const instance = new type();
vnode.component = instance;
}
return vnode;
}构建流程:
- 通过
createVNode创建虚拟节点 - 通过
render函数生成真实 DOM - 通过
diff算法进行更新
七、进阶使用
1. 自定义指令
// directives.js
export default {
'v-focus': {
mounted(el) {
el.focus();
}
}
}<!-- 使用自定义指令 -->
<input v-focus />2. 状态管理方案比较
| 方案 | 适用场景 | 优点 | 缺点 |
|---|---|---|---|
| Vuex | 大型应用 | 状态集中管理 | 配置复杂 |
| Pinia | 中小型项目 | 简单易用 | 功能较 Vuex 简单 |
| LocalStorage | 简单缓存 | 无需额外依赖 | 无法响应式更新 |
推荐方案:
- 新项目优先使用 Pinia
- 复杂项目使用 Vuex + modules
- 需要持久化存储时结合 LocalStorage
3. 路由高级用法
// 路由配置
const routes = [
{
path: '/user/:id',
component: UserComponent,
props: (route) => ({
userId: route.params.id
})
}
];动态路由:
- 使用
params提取动态参数 - 通过
props将参数传递给组件 - 支持嵌套路由和命名路由
八、性能与工程实践
1. 性能优化技巧
关键优化点:
| 优化策略 | 说明 | 示例 |
|---|---|---|
| v-once | 防止重复渲染 | {{ data }} |
| v-memo | 按 key 缓存组件 | |
| keep-alive | 缓存组件状态 | |
| 避免深层克隆 | 减少不必要的计算 | 使用 Object.freeze() 防止修改 |
实际应用:
- 大型列表使用
v-memo缓存渲染 - 搜索功能使用
v-once防止重复渲染 - 常用组件使用
keep-alive缓存
2. 安全风险防范
常见风险:
| 风险类型 | 说明 | 防范措施 |
|---|---|---|
| XSS 攻击 | 动态插入未过滤的 HTML | 使用 v-html 时过滤内容 |
| 跨站脚本 | 非法数据注入 | 使用 encodeURIComponent() 编码 |
| 跨站请求伪造 | 未验证请求来源 | 使用 CSRF token 验证 |
防范措施:
- 使用
v-html时添加sanitize处理 - 对用户输入进行严格校验
- 使用 Content Security Policy (CSP) 防止注入攻击
九、常见问题与踩坑
1. 常见错误示例
错误代码:
// 错误:直接修改 props
function updateMessage() {
this.message = 'New message'; // ❌ 错误写法
}正确写法:
function updateMessage() {
this.$emit('update', 'New message'); // ✅ 正确写法
}错误原因:直接修改 props 会破坏响应性
2. 常见坑点分析
| 坑点 | 说明 | 解决方案 |
|---|---|---|
| 数组更新无效 | 使用 push/pop 等方法无法触发更新 | 使用 Vue.set 或 this.$set |
| 组件未渲染 | 忘记调用 this.$nextTick | 在 DOM 更新后使用 nextTick |
| 路由参数丢失 | 未正确传递 props | 使用 props 配置项或 params |
| 状态未更新 | 忘记使用 value 访问响应式数据 | 确保所有访问都通过 value |
十、最佳实践
1. 推荐开发规范
- 使用
script setup语法 - 保持组件单一职责
- 使用
key优化列表渲染 - 避免过度使用
v-if/v-show - 使用
v-model实现双向绑定
2. 推荐工具链
| 工具 | 作用 | 推荐版本 |
|---|---|---|
| ESLint | 代码规范 | 8.x |
| Prettier | 代码格式化 | 3.x |
| Vite | 构建工具 | 3.x |
| Vitest | 单元测试 | 1.x |
| Storybook | 组件文档 | 1.x |
3. 推荐开发模式
- 使用
Vue 3的 Composition API - 结合
TypeScript提升类型安全 - 使用
Vue Router 4实现路由管理 - 使用
Vuex或Pinia管理全局状态 - 使用
Vite加速开发流程
十一、总结
Vue 的核心价值在于其响应式系统和组件化开发模式,但实际开发中需要关注:
- 响应性失效:通过
ref/reactive正确管理响应式数据 - 组件通信:合理使用 props 和 events 实现父子通信
- 性能优化:通过
v-once/v-memo等指令优化渲染性能 - 状态管理:根据项目规模选择合适的状态管理方案
- 安全风险:防范 XSS 攻击和数据注入
在实际项目中,建议:
- 中小型项目使用
Pinia管理状态 - 大型项目使用
Vuex+modules - 使用
TypeScript提升类型安全 - 避免直接修改 props 和 array 原生方法
- 通过
Vite加速开发流程
通过深入理解 Vue 的核心机制,结合实际场景选择合适的解决方案,可以显著提升开发效率和项目质量。
评论已关闭