Vue 基础与进阶
约 3002 字大约 10 分钟
Vue 基础与进阶
Vue核心概念 🟢
1. Vue实例创建和配置
// Vue2 创建实例
const vm = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
},
methods: {
greet() {
console.log(this.message);
}
}
});
// Vue3 创建实例
const app = createApp({
data() {
return {
message: 'Hello Vue 3!'
}
},
methods: {
greet() {
console.log(this.message);
}
}
});
app.mount('#app');
2. 模板语法
<!-- 文本插值 -->
<div>{{ message }}</div>
<!-- 原始HTML -->
<div v-html="rawHtml"></div>
<!-- 属性绑定 -->
<div v-bind:id="dynamicId"></div>
<div :class="{ active: isActive }"></div>
<div :style="{ color: activeColor }"></div>
<!-- 条件渲染 -->
<div v-if="type ``` 'A'">A</div>
<div v-else-if="type ``` 'B'">B</div>
<div v-else>C</div>
<div v-show="isVisible">Show/Hide</div>
<!-- 列表渲染 -->
<ul>
<li v-for="(item, index) in items" :key="item.id">
{{ index }} - {{ item.name }}
</li>
</ul>
<!-- 事件���理 -->
<button v-on:click="handleClick">Click</button>
<button @click="handleClick($event)">Click with Event</button>
<!-- 表单输入绑定 -->
<input v-model="message">
<input v-model.trim="message">
<input v-model.number="age">
Vue生命周期 🟡
1. Vue2生命周期
export default {
// 实例创建前
beforeCreate() {
// data和methods还未初始化
},
// 实例创建后
created() {
// 可以访问data和methods
// 适合做异步请求
},
// 挂载前
beforeMount() {
// 模板编译完成,还未渲染DOM
},
// 挂载后
mounted() {
// DOM已渲染完成
// 可以访问DOM元素
},
// 更新前
beforeUpdate() {
// 数据已更新,DOM还未更新
},
// 更新后
updated() {
// DOM已更新完成
},
// 销毁前
beforeDestroy() {
// 实例还可用,适合清理工作
},
// 销毁后
destroyed() {
// 实例已销毁
}
}
2. Vue3生命周期
import { onMounted, onUpdated, onUnmounted } from 'vue';
export default {
setup() {
// 创建后
onBeforeMount(() => {
// 组件挂载前
});
onMounted(() => {
// 组件挂载后
});
// 更新时
onBeforeUpdate(() => {
// 组件更新前
});
onUpdated(() => {
// 组件更新后
});
// 销毁时
onBeforeUnmount(() => {
// 组件卸载前
});
onUnmounted(() => {
// 组件卸载后
});
}
}
Vue3 Composition API 🔴
1. 响应式系统
import { ref, reactive, computed, watch } from 'vue';
export default {
setup() {
// ref用于基本类型
const count = ref(0);
// reactive用于对象
const state = reactive({
user: {
name: 'John',
age: 30
}
});
// 计算属性
const doubleCount = computed(() => count.value * 2);
// 监听器
watch(count, (newValue, oldValue) => {
console.log(`Count changed from ${oldValue} to ${newValue}`);
});
// 深度监听
watch(() => state.user, (newValue, oldValue) => {
console.log('User changed:', newValue);
}, { deep: true });
return {
count,
state,
doubleCount
};
}
}
2. 生命周期钩子
import {
onBeforeMount,
onMounted,
onBeforeUpdate,
onUpdated,
onBeforeUnmount,
onUnmounted,
onErrorCaptured
} from 'vue';
export default {
setup() {
onBeforeMount(() => {
// 组件挂载前
});
onMounted(() => {
// 组件挂载后
});
onBeforeUpdate(() => {
// 组件更新前
});
onUpdated(() => {
// 组件更新后
});
onBeforeUnmount(() => {
// 组件卸载前
});
onUnmounted(() => {
// 组件卸载后
});
onErrorCaptured((err, instance, info) => {
// 错误处理
console.error(err);
return false; // 阻止错误继续传播
});
}
}
3. 依赖注入
// 父组件提供数据
import { provide, ref } from 'vue';
export default {
setup() {
const theme = ref('dark');
provide('theme', theme);
return {
theme
};
}
}
// 子组件注入数据
import { inject } from 'vue';
export default {
setup() {
const theme = inject('theme', 'light'); // 第二个参数为默认值
return {
theme
};
}
}
Vue Router详解 🟡
1. 路由配置
// Vue2路由配置
import Vue from 'vue';
import VueRouter from 'vue-router';
Vue.use(VueRouter);
const routes = [
{
path: '/',
name: 'Home',
component: Home,
meta: { requiresAuth: true }
},
{
path: '/about',
name: 'About',
// 路由懒加载
component: () => import('./views/About.vue')
},
{
path: '/user/:id',
name: 'User',
component: User,
// 嵌套路由
children: [
{
path: 'profile',
component: UserProfile
}
]
}
];
const router = new VueRouter({
mode: 'history',
base: process.env.BASE_URL,
routes
});
// 导航守卫
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.isLoggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
});
} else {
next();
}
} else {
next();
}
});
// Vue3路由配置
import { createRouter, createWebHistory } from 'vue-router';
const router = createRouter({
history: createWebHistory(),
routes: [
// 路由配置
]
});
2. 路由使用
<!-- 路由链接 -->
<router-link to="/" active-class="active">Home</router-link>
<router-link :to="{ name: 'User', params: { id: 123 }}">User</router-link>
<!-- 路由视图 -->
<router-view></router-view>
<!-- 命名视图 -->
<router-view name="sidebar"></router-view>
<router-view name="main"></router-view>
// 编程式导航
export default {
methods: {
goToUser() {
this.$router.push({ name: 'User', params: { id: 123 }});
},
goBack() {
this.$router.go(-1);
}
}
}
Vuex状态管理 🟡
1. Store配置
// Vue2 Vuex配置
import Vue from 'vue';
import Vuex from 'vuex';
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0,
user: null
},
getters: {
doubleCount: state => state.count * 2,
isLoggedIn: state => !!state.user
},
mutations: {
INCREMENT(state) {
state.count++;
},
SET_USER(state, user) {
state.user = user;
}
},
actions: {
async login({ commit }, credentials) {
try {
const user = await authService.login(credentials);
commit('SET_USER', user);
return user;
} catch (error) {
console.error('Login failed:', error);
throw error;
}
}
},
modules: {
// 模块化配置
products: {
namespaced: true,
state: { /*...*/ },
mutations: { /*...*/ },
actions: { /*...*/ }
}
}
});
// Vue3 Pinia配置
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
state: () => ({
user: null,
isLoggedIn: false
}),
getters: {
userName: state => state.user?.name
},
actions: {
async login(credentials) {
const user = await authService.login(credentials);
this.user = user;
this.isLoggedIn = true;
}
}
});
2. Store使用
// Vue2 Vuex使用
export default {
computed: {
...mapState(['count', 'user']),
...mapGetters(['doubleCount', 'isLoggedIn'])
},
methods: {
...mapMutations(['INCREMENT']),
...mapActions(['login']),
async handleLogin() {
try {
await this.login({
username: 'user',
password: 'pass'
});
} catch (error) {
console.error(error);
}
}
}
}
// Vue3 Pinia使用
import { useUserStore } from '@/stores/user';
export default {
setup() {
const userStore = useUserStore();
const handleLogin = async () => {
try {
await userStore.login({
username: 'user',
password: 'pass'
});
} catch (error) {
console.error(error);
}
};
return {
user: computed(() => userStore.user),
isLoggedIn: computed(() => userStore.isLoggedIn),
handleLogin
};
}
}
Vue3新特性 🔴
1. Composition API vs Options API
// Options API
export default {
data() {
return {
count: 0,
name: 'John'
};
},
computed: {
doubleCount() {
return this.count * 2;
}
},
methods: {
increment() {
this.count++;
}
},
mounted() {
console.log('Component mounted');
}
}
// Composition API
import { ref, computed, onMounted } from 'vue';
export default {
setup() {
const count = ref(0);
const name = ref('John');
const doubleCount = computed(() => count.value * 2);
const increment = () => {
count.value++;
};
onMounted(() => {
console.log('Component mounted');
});
return {
count,
name,
doubleCount,
increment
};
}
}
2. Teleport组件
<!-- 传送门组件 -->
<template>
<button @click="showModal = true">Show Modal</button>
<teleport to="body">
<div v-if="showModal" class="modal">
<h2>Modal Title</h2>
<p>Modal content...</p>
<button @click="showModal = false">Close</button>
</div>
</teleport>
</template>
<script>
import { ref } from 'vue';
export default {
setup() {
const showModal = ref(false);
return { showModal };
}
}
</script>
3. Suspense组件
<template>
<suspense>
<template #default>
<async-component />
</template>
<template #fallback>
<loading-spinner />
</template>
</suspense>
</template>
<script>
// 异步组件
const AsyncComponent = {
async setup() {
const data = await fetchData();
return { data };
}
};
</script>
性能优化 🔴
1. 编译优化
// 静态提升
// Vue3会自动提升静态内容
const hoisted = createVNode("div", { class: "static" }, "Static Content");
// 事件缓存
// Vue3会自动缓存事件处理函数
const cached = e => count.value++;
// Patch Flag优化
// Vue3会标记动态内容
createVNode("div", { class: "dynamic" }, text.value, 1 /* TEXT */);
2. 渲染优化
// 使用v-show代替v-if
<template>
<div v-show="isVisible">
<!-- 复杂的内容 -->
</div>
</template>
// 使用v-once处理静态内容
<template>
<div v-once>
<!-- 永远不会改变的内容 -->
</div>
</template>
// 使用v-memo缓存
<template>
<div v-memo="[item.id]">
<!-- 只有item.id改变时才会重新渲染 -->
</div>
</template>
3. 代码分割
// 路由懒加载
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
}
];
// 组件懒加载
const MyComponent = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
);
// 条件加载
if (condition) {
const module = await import('./module');
}
Vue3高级特性补充 🔴
1. Vue3响应式原理
// Vue3响应式系统实现原理
function reactive(target) {
// 基础类型使用ref
if (typeof target !== 'object' || target === null) {
return ref(target);
}
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
const res = Reflect.get(target, key, receiver);
// 深层响应式
return typeof res === 'object' ? reactive(res) : res;
},
set(target, key, value, receiver) {
const oldValue = target[key];
const result = Reflect.set(target, key, value, receiver);
// 触发更新
if (hasChanged(value, oldValue)) {
trigger(target, key);
}
return result;
},
deleteProperty(target, key) {
const hadKey = hasOwn(target, key);
const result = Reflect.deleteProperty(target, key);
if (hadKey && result) {
// 触发更新
trigger(target, key);
}
return result;
}
});
return proxy;
}
// ref实现
function ref(value) {
if (isRef(value)) {
return value;
}
return new RefImpl(value);
}
class RefImpl {
constructor(value) {
this._value = convert(value);
this.__v_isRef = true;
}
get value() {
track(this, 'value');
return this._value;
}
set value(newValue) {
if (hasChanged(newValue, this._value)) {
this._value = convert(newValue);
trigger(this, 'value');
}
}
}
2. 自定义渲染器
// 创建自定义渲染器
const { createRenderer } = Vue;
const renderer = createRenderer({
createElement(type) {
// 创建元素
return document.createElement(type);
},
setElementText(node, text) {
// 设置元素文本
node.textContent = text;
},
insert(child, parent, anchor = null) {
// 插入元素
parent.insertBefore(child, anchor);
},
patchProp(el, key, prevValue, nextValue) {
// 处理属性
if (key === 'class') {
el.className = nextValue || '';
} else if (key === 'style') {
if (nextValue) {
for (const styleName in nextValue) {
el.style[styleName] = nextValue[styleName];
}
}
} else if (/^on[^a-z]/.test(key)) {
const eventName = key.slice(2).toLowerCase();
if (prevValue) {
el.removeEventListener(eventName, prevValue);
}
if (nextValue) {
el.addEventListener(eventName, nextValue);
}
} else {
if (nextValue === null || nextValue === false) {
el.removeAttribute(key);
} else {
el.setAttribute(key, nextValue);
}
}
},
// 其他必要的渲染方法...
});
// 使用自定义渲染器
const app = {
setup() {
const count = ref(0);
return { count };
},
render() {
return h('div', { class: 'container' }, [
h('button', {
onClick: () => this.count++
}, `Count: ${this.count}`)
]);
}
};
// 挂载应用
renderer.createApp(app).mount('#app');
3. 高级组件模式
1. 函数式组件
// 函数式组件定义
const FunctionalComponent = (props, { slots, emit, attrs }) => {
return h('div', {
class: props.class,
onClick: () => emit('click')
}, slots.default?.());
};
FunctionalComponent.props = {
class: String
};
// 使用函数式组件
const app = {
components: {
FunctionalComponent
},
template: `
<FunctionalComponent
class="my-component"
@click="handleClick"
>
Content
</FunctionalComponent>
`
};
2. 异步组件
// 异步组件定义
const AsyncComponent = defineAsyncComponent({
loader: () => import('./components/Heavy.vue'),
loadingComponent: LoadingComponent,
errorComponent: ErrorComponent,
delay: 200,
timeout: 3000,
suspensible: true,
onError(error, retry, fail, attempts) {
if (attempts <= 3) {
retry();
} else {
fail();
}
}
});
// 使用异步组件
const app = {
components: {
AsyncComponent
},
template: `
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<div>Loading...</div>
</template>
</Suspense>
`
};
4. 高级Composition API使用
1. 自定义Hook封装
// 封装请求Hook
function useRequest(url, options = {}) {
const data = ref(null);
const error = ref(null);
const loading = ref(true);
const {
immediate = true,
initialData = null,
transform = data => data,
onSuccess = () => {},
onError = () => {}
} = options;
const execute = async () => {
loading.value = true;
error.value = null;
try {
const response = await fetch(url);
const result = await response.json();
data.value = transform(result);
onSuccess(data.value);
} catch (err) {
error.value = err;
onError(err);
} finally {
loading.value = false;
}
};
if (immediate) {
execute();
}
return {
data,
error,
loading,
execute
};
}
// 使用自定义Hook
const UserProfile = {
setup() {
const { data: user, loading, error } = useRequest('/api/user', {
transform: data => ({
...data,
fullName: `${data.firstName} ${data.lastName}`
}),
onSuccess: (user) => {
console.log('User loaded:', user);
}
});
return { user, loading, error };
}
};
2. 状态管理Hook
// 创建全局状态Hook
function createGlobalState(initialState) {
const state = reactive(initialState);
const stateRefs = new Set();
function useState() {
const localState = toRefs(state);
stateRefs.add(localState);
onUnmounted(() => {
stateRefs.delete(localState);
});
return {
...localState,
reset: () => {
Object.assign(state, initialState);
}
};
}
return useState;
}
// 使用全局状态
const useUserState = createGlobalState({
user: null,
isLoggedIn: false
});
const LoginComponent = {
setup() {
const { user, isLoggedIn, reset } = useUserState();
const login = async (credentials) => {
// 登录逻辑
user.value = await loginAPI(credentials);
isLoggedIn.value = true;
};
const logout = () => {
reset();
};
return {
user,
isLoggedIn,
login,
logout
};
}
};
5. 性能优化技巧
1. 虚拟列表实现
const VirtualList = {
props: {
items: Array,
itemHeight: Number,
visibleItems: Number
},
setup(props) {
const containerRef = ref(null);
const scrollTop = ref(0);
const visibleData = computed(() => {
const start = Math.floor(scrollTop.value / props.itemHeight);
const end = start + props.visibleItems;
return props.items
.slice(start, end)
.map((item, index) => ({
...item,
style: {
position: 'absolute',
top: (start + index) * props.itemHeight + 'px',
height: props.itemHeight + 'px'
}
}));
});
const containerHeight = computed(() =>
props.items.length * props.itemHeight
);
const handleScroll = (e) => {
scrollTop.value = e.target.scrollTop;
};
return {
containerRef,
visibleData,
containerHeight,
handleScroll
};
},
template: `
<div
ref="containerRef"
@scroll="handleScroll"
:style="{
height: visibleItems * itemHeight + 'px',
overflow: 'auto',
position: 'relative'
}"
>
<div :style="{ height: containerHeight + 'px' }">
<div
v-for="item in visibleData"
:key="item.id"
:style="item.style"
>
<slot name="item" :item="item">
{{ item.text }}
</slot>
</div>
</div>
</div>
`
};
2. 动态组件优化
// 组件缓存
const CachedComponent = {
template: `
<keep-alive
:include="['ComponentA', 'ComponentB']"
:max="10"
>
<component :is="currentComponent" />
</keep-alive>
`
};
// 异步组件加载优化
const AsyncComponent = defineAsyncComponent({
loader: () => import('./HeavyComponent.vue'),
delay: 200,
timeout: 3000,
errorComponent: ErrorComponent,
loadingComponent: LoadingComponent
});
Vue高级面试题 🔴
1. Vue3响应式系统相关
Q1: Vue3的响应式系统相比Vue2有哪些改进? A1:
基于Proxy的响应式系统:
- 可以监听属性的添加和删除
- 可以监听数组索引和length的变化
- 可以监听Map、Set等数据结构
- 性能更好,不需要递归遍历
示例代码:
// Vue2的响应式系统
Object.defineProperty(obj, 'key', {
get() {
// 依赖收集
return value;
},
set(newValue) {
// 触发更新
value = newValue;
}
});
// Vue3的响应式系统
const proxy = new Proxy(target, {
get(target, key, receiver) {
// 依赖收集
track(target, key);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
// 触发更新
const result = Reflect.set(target, key, value, receiver);
trigger(target, key);
return result;
}
});
Q2: Vue3的Composition API相比Options API有哪些优势? A2:
- 更好的代码组织:
// Options API
export default {
data() {
return {
user: null,
loading: false
};
},
methods: {
async fetchUser() {
this.loading = true;
try {
this.user = await api.getUser();
} finally {
this.loading = false;
}
}
},
mounted() {
this.fetchUser();
}
};
// Composition API
export default {
setup() {
const user = ref(null);
const loading = ref(false);
const fetchUser = async () => {
loading.value = true;
try {
user.value = await api.getUser();
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchUser();
});
return { user, loading, fetchUser };
}
};
- 更好的逻辑复用:
// 可复用的Hook
function useUser() {
const user = ref(null);
const loading = ref(false);
const fetchUser = async () => {
loading.value = true;
try {
user.value = await api.getUser();
} finally {
loading.value = false;
}
};
onMounted(() => {
fetchUser();
});
return { user, loading, fetchUser };
}
// 在组件中使用
export default {
setup() {
const { user, loading, fetchUser } = useUser();
return { user, loading, fetchUser };
}
};
2. Vue性能优化相关
Q1: Vue3中如何优化大型应用的性能? A1:
- 代码分割:
// 路由级别的代码分割
const routes = [
{
path: '/dashboard',
component: () => import('./views/Dashboard.vue')
}
];
// 组件级别的代码分割
const AsyncComponent = defineAsyncComponent(() =>
import('./components/Heavy.vue')
);
- 虚拟列表:
const VirtualList = {
props: ['items'],
setup(props) {
const visibleItems = computed(() => {
// 计算可见项
return props.items.slice(startIndex, endIndex);
});
return { visibleItems };
}
};
- 组件缓存:
<keep-alive :include="['ComponentA']" :max="10">
<component :is="currentComponent" />
</keep-alive>
Q2: Vue3的编译优化有哪些? A2:
- 静态提升:
// 编译前
<template>
<div class="static">
<span>Static Content</span>
<span>{{ dynamic }}</span>
</div>
</template>
// 编译后
const _hoisted_1 = /*#__PURE__*/createVNode("span", null, "Static Content", -1);
export function render(_ctx, _cache) {
return (openBlock(), createBlock("div", { class: "static" }, [
_hoisted_1,
createVNode("span", null, toDisplayString(_ctx.dynamic), 1)
]));
}
- Patch Flag优化:
// 编译前
<div :id="id" class="static" :class="dynamic">
{{ text }}
</div>
// 编译后
createVNode("div", {
id: id,
class: ["static", dynamic]
}, text,
8 /* PROPS */,
["id", "class"]
)
3. Vue Router相关
Q1: Vue Router的导航守卫有哪些?如何使用? A1:
// 全局前置守卫
router.beforeEach((to, from, next) => {
if (to.matched.some(record => record.meta.requiresAuth)) {
if (!auth.isLoggedIn()) {
next({
path: '/login',
query: { redirect: to.fullPath }
});
} else {
next();
}
} else {
next();
}
});
// 路由独享守卫
const routes = [
{
path: '/users/:id',
component: UserDetails,
beforeEnter: (to, from, next) => {
// 验证用户权限
if (hasPermission(to.params.id)) {
next();
} else {
next('/403');
}
}
}
];
// 组件内守卫
export default {
beforeRouteEnter(to, from, next) {
// 在渲染该组件的对应路由被验证前调用
next(vm => {
// 通过vm访问组件实例
});
},
beforeRouteUpdate(to, from, next) {
// 在当前路由改变,但是该组件被复用时调用
next();
},
beforeRouteLeave(to, from, next) {
// 导航离开该组件的对应路由时调用
const answer = window.confirm('确定要离开吗?');
if (answer) {
next();
} else {
next(false);
}
}
};
[未完待续...]