webpack & vite 构建工具对比
1. 构建方式
- webpack:采用打包的方式,首先将所有的模块和资源静态分析,生成一个或多个包含所有依赖的打包文件。这个过程在开发过程中可能较慢,因为每次代码修改后都需要重新打包。
- Vite:使用原生的ES模块(ESM),支持热模块替换(HMR),在开发时不进行打包,而是通过服务器直接提供文件,速度更快。只有在生产环境中,Vite才会打包代码(使用rollup进行打包)。因此开发节点优势明显,vite使用的是ES Module,因此在代码中不可使用Commonjs。
2. 启动速度
- webpack:首次构建较慢,尤其是在项目较大或者依赖较多的时候。增量构建时可能会有所改善,但整体来说启动速度一般。
- Vite:由于不进行打包(不需要编译,不需要分析模块依赖),启动速度非常快,通常能够在几秒钟内启动开发服务器。(项目越复杂模块越多,vite优势越明显)
3. 热模块替换(HMR)
- webpack:支持HMR,但在某些情况下(如全局状态变更)可能会导致页面闪烁。(需要把该模块的相关依赖全部编译一遍)
- Vite:HMR机制非常高效,能够在大部分情况下保持应用状态,更新速度也很快。(改动一个模块仅需要让浏览器重新请求该模块即可)
4. 配置复杂度
- webpack:配置相对复杂,需要许多插件和loader来处理不同类型的文件。对于新手来说,学习曲线较陡。
- Vite:配置简单,提高了开发效率,内置了许多常用功能,适合快速开发和原型设计。
5. 插件生态
- webpack:拥有丰富的插件生态,几乎可以处理任何构建需求,适合大型和复杂项目。
- Vite:虽然插件生态相对较新,但正在快速发展,支持多种插件来扩展功能。
6. 性能
- webpack:在生产构建时性能强大,能够进行代码分割、懒加载等优化。
- Vite:在开发时性能优越,但在某些情况下,生产构建相较于webpack可能稍显不足(不过随着Vite不断更新,这一点在逐渐改善)。
7. 支持的框架 (兼容性)
- webpack:兼容性好,支持React、Vue、Angular等多种框架。
- Vite:同样支持现代框架(如Vue 3、React、Svelte等),并提供了以"框架"为中心的优化。
Vue 3 实现了重大的性能提升体现的具体方面
1. 静态提升(Static Tree Hoisting)
html
<template>
<div>
<h1>Hello, World!</h1> <!-- 静态提升 -->
<p>{{ message }}</p>
</div>
</template>
<script>
// `<h1>Hello, World!</h1>` 是一个静态节点,Vue 3 会在编译时识别并提升它,避免在每次渲染时都重新创建
export default {
data() {
return {
message: 'Welcome to Vue 3!'
};
}
}
</script>
静态提升是指在编译阶段识别出不变的部分,将其提升为静态节点,从而避免不必要的重渲染。
- 静态节点:对于那些不会改变的节点,Vue 3 会在编译时将它们提取出来,只在首次渲染时创建,而后续更新时不需要再次处理这些节点。
- 减少重渲染:通过静态提升,Vue 3 减少了对于静态内容的重复渲染,从而提高了性能,特别是在大型列表和复杂组件中。
2. 预字符串化(Pre-Rendering)
html
<template>
<p>{{ concatenatedMessage }}</p>
</template>
<script setup>
const hello = 'Hello';
const world = 'World';
const concatenatedMessage = `${hello}, ${world}!`; // 预字符串化
</script>
预字符串化是指将模板编译为字符串形式,并在运行时直接使用。
- 编译优化:在运行时直接使用预编译的字符串,避免了复杂的解析过程。
- 提高渲染速度:通过减少不必要的编译和解析步骤,提升了渲染速度。
3. 缓存事件处理函数(Cache Event Handler Functions)
html
<template>
<button @click="handleClick">Click me</button>
</template>
<script>
// 在 Vue 3 中,`handleClick` 函数只会创建一次,并被缓存,当组件更新时不会重新创建
export default {
methods: {
handleClick() {
console.log('Button clicked!');
}
}
}
</script>
Vue 3 对事件处理函数进行了缓存优化。
- 函数重用:在组件更新时,重复的事件处理函数不会被重新创建,而是复用先前创建的函数。
- 提升性能:这样做减少了内存开销和函数创建的次数,提高了性能和效率。
4. 块树(Block Tree)
html
<template>
<div>
<h1>{{ title }}</h1>
<p v-for="item in items" :key="item.id">{{ item.text }}</p>
</div>
</template>
<script>
// `<h1>` 和 `<p>` 组成了不同的块,Vue 3 可以单独处理它们的更新。
// 例如,如果 `title` 修改,只有 `<h1>` 会被重新渲染
export default {
data() {
return {
title: 'Items',
items: [
{ id: 1, text: 'Item 1' },
{ id: 2, text: 'Item 2' }
]
};
}
}
</script>
块树是指在虚拟 DOM 中引入的一种新数据结构,用于优化渲染。
- 组件分块:将组件分为多个块,每个块独立处理自己的更新,提升了更新时的粒度。
- 控制渲染:通过高效地管理不同块之间的逻辑关系,Vue 3 可以更精准地控制哪些部分需要重新渲染,从而提高整体性能。
5. 补丁标记(Patch Flags)
html
<template>
<div>
<h1>{{ title }}</h1>
<button @click="changeTitle">Change Title</button>
</div>
</template>
<script>
// 当 `title` 改变时,补丁标记可以标识出这是一处"文本更新",而不是解析整个组件,这样可以更高效地进行 DOM 更新
export default {
data() {
return {
title: 'Initial Title'
};
},
methods: {
changeTitle() {
this.title = 'Updated Title';
}
}
}
</script>
补丁标记是一种机制,用于标识虚拟 DOM 更新的类型。
- 优化更新过程:通过使用补丁标记,Vue 3 可以在 diff 过程中快速识别哪些节点发生了变化,并以最小的成本进行更新。
- 细粒度更新:补丁标记的引入使得 Vue 3 的更新变得更加细粒度,这样在页面更新时可以避免不必要的完整渲染,进一步提升性能。
为何vue3去掉了构造函数
1. 简化 API 设计
在 Vue 2 中,开发者需要通过 new Vue()
构造函数来创建应用实例,这种方式虽然直观,但在大型项目中可能会导致代码冗余和复杂性增加。Vue 3 引入了 createApp
函数,使得创建应用实例更加简洁和直观
js
// Vue 2 即是一个应用又是一个特殊的vue组件 概念模糊
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue 2!'
}
});
// Vue 3 把组件实例和应用分开 提供的方式的针对整个应用 而不是一个特殊的组件
import { createApp } from 'vue';
const app = createApp({
data() {
return {
message: 'Hello Vue 3!'
};
}
});
app.mount('#app');
2. 提高灵活性
Vue 2 的构造函数方式限制了应用的扩展性。例如,全局配置(如 Vue.use
、Vue.mixin
等)会影响到所有实例,这在某些场景下可能会导致冲突或不可预期的行为。
Vue 3 通过 createApp
创建的应用实例是独立的,每个实例可以有自己的配置,避免了全局污染:
js
const app1 = createApp({});
app1.use(plugin1);
const app2 = createApp({});
app2.use(plugin2);
3. Tree Shaking
vue2构造函数集成了太多功能,不利于Tree Shaking,vue3把这些功能使用普通函数导出充分使用Tree Shaking 减少打包体积。
小结
Vue 3 去掉构造函数,改用 createApp
函数,主要是为了:
- 简化 API 设计,使代码更简洁;
- 提高应用的灵活性和隔离性;
- 更好地支持 TypeScript;
- 符合现代 JavaScript 生态;
- 优化 Tree Shaking,减小打包体积;
- 提供更清晰的代码结构。
vue3数据响应式的理解
vue不再不在使用
Object.defineProperty 方式定义完成数据响应式,而是使用
Proxy, 除了
Proxy本身效率比
Object.defineProperty高,由于不用遍历所有属性直接得到一个
Proxy,所以vue3中对数据的访问是动态的,访问某个属性时候再动态的获取和设置,极大提升了组件初始阶段的效率问题。同时,
Proxy可以监控到成员的新增和删除,并且均可以出发视图重新渲染 这些在
vue2`中直接是难以做到的。
1. Proxy 和 Reflect
Vue 3 使用 Proxy
来拦截对象的操作(如读取、写入、删除等),并通过 Reflect
来执行这些操作。Proxy
可以监听对象的所有属性变化,而无需像 Vue 2 那样通过 Object.defineProperty
逐个定义属性的 getter
和 setter
。
js
const target = { count: 0 };
const handler = {
get(target, key, receiver) {
console.log(`读取属性: ${key}`);
return Reflect.get(target, key, receiver);
},
set(target, key, value, receiver) {
console.log(`设置属性: ${key} = ${value}`);
return Reflect.set(target, key, value, receiver);
}
};
const proxy = new Proxy(target, handler);
proxy.count; // 输出: 读取属性: count
proxy.count = 1; // 输出: 设置属性: count = 1
2. Reactive 和 Ref
Vue 3 提供了 reactive
和 ref
两个函数来创建响应式数据。
reactive
:用于将对象转换为响应式对象。ref
:用于将基本类型数据(如number
、string
等)转换为响应式对象,通过.value
访问和修改值。
js
import { reactive, ref } from 'vue';
// 响应式对象
const state = reactive({ count: 0 });
state.count++; // 自动触发更新
// 响应式基本类型
const count = ref(0);
count.value++; // 自动触发更新
3. 依赖收集与触发更新
Vue 3 的响应式系统通过依赖收集和触发更新来实现数据的动态响应。
- 依赖收集:当组件渲染时,Vue 会追踪哪些响应式数据被使用,并建立依赖关系。
- 触发更新:当响应式数据发生变化时,Vue 会通知所有依赖该数据的组件进行重新渲染。
js
import { reactive, watchEffect } from 'vue';
const state = reactive({ count: 0 });
// 依赖收集
watchEffect(() => {
console.log(`count 的值是: ${state.count}`);
});
// 触发更新
state.count++; // 输出: count 的值是: 1
4. Computed 和 Watch
Vue 3 提供了 computed
和 watch
来处理复杂的响应式逻辑。
computed
:用于创建基于响应式数据的计算属性。watch
:用于监听响应式数据的变化并执行回调函数。
js
import { reactive, computed, watch } from 'vue';
const state = reactive({ count: 0 });
// 计算属性
const doubleCount = computed(() => state.count * 2);
// 监听器
watch(() => state.count, (newValue, oldValue) => {
console.log(`count 从 ${oldValue} 变为 ${newValue}`);
});
state.count++; // 输出: count 从 0 变为 1
5. 响应式系统的优势
- 性能更好 :
Proxy
可以监听整个对象,无需像Object.defineProperty
那样逐个定义属性。 - 支持更多操作 :
Proxy
可以拦截更多操作,如deleteProperty
、has
等。 - 更灵活 :
reactive
和ref
提供了更灵活的方式来创建响应式数据。
vue3中v-model的变化
1. 更灵活的自定义组件支持
在Vue 3中,你可以通过指定不同的属性名和事件来改变v-model
的行为。
父组件:
html
<template>
<CustomInput v-model="message" />
</template>
<script>
import { ref } from 'vue';
import CustomInput from './CustomInput.vue';
export default {
components: { CustomInput },
setup() {
const message = ref('Hello Vue 3');
return { message };
}
};
</script>
子组件 (CustomInput.vue):
html
<template>
<input :value="modelValue" @input="$emit('update:modelValue', $event.target.value)" />
</template>
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
};
</script>
2. 多值绑定
Vue 3允许在一个组件上使用多个v-model
绑定。
父组件:
html
<template>
<UserForm v-model:name="name" v-model:age="age" />
</template>
<script>
import { ref } from 'vue';
import UserForm from './UserForm.vue';
export default {
components: { UserForm },
setup() {
const name = ref('John');
const age = ref(30);
return { name, age };
}
};
</script>
子组件 (UserForm.vue):
html
<template>
<div>
<input :value="name" @input="$emit('update:name', $event.target.value)" placeholder="Name" />
<input :value="age" @input="$emit('update:age', $event.target.value)" placeholder="Age" />
</div>
</template>
<script>
export default {
props: ['name', 'age'],
emits: ['update:name', 'update:age']
};
</script>
3. 性能优化
虽然性能优化是内部实现的变化,但你可以在实际项目中感受到更流畅的数据更新。这里不再提供具体代码示例,因为这是框架层面的改进。
4. 移除.sync修饰符
在Vue 3中,.sync
修饰符已经被移除。你可以使用命名的v-model
来替代它。
旧方式(Vue 2):
html
<ChildComponent :count.sync="parentCount" />
新方式(Vue 3):
html
<ChildComponent v-model:count="parentCount" />
5. 自定义v-model指令
虽然官方文档中并没有提到通过Vue.directive
来定义自己的v-model
指令,但在Vue 3中,你可以通过组件选项来定制v-model
的行为。
子组件 (CustomCheckbox.vue):
html
<template>
<label>
<input type="checkbox" :checked="modelValue" @change="$emit('update:modelValue', $event.target.checked)" />
{{ label }}
</label>
</template>
<script>
export default {
props: {
modelValue: Boolean,
label: String
},
emits: ['update:modelValue']
};
</script>
父组件:
html
<template>
<CustomCheckbox v-model="isChecked" label="Check me!" />
</template>
<script>
import { ref } from 'vue';
import CustomCheckbox from './CustomCheckbox.vue';
export default {
components: { CustomCheckbox },
setup() {
const isChecked = ref(false);
return { isChecked };
}
};
</script>
清醒认识vue3异步组件用法
在 Vue 3 中,可以使用 defineAsyncComponent
来定义异步组件。它接受一个返回 Promise
的工厂函数,通常用于动态导入组件
1. 基本用法
在 Vue 3 中,可以使用 defineAsyncComponent
来定义异步组件。它接受一个返回 Promise
的工厂函数,通常用于动态导入组件。
js
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
);
export default {
components: {
AsyncComponent
}
};
2. 加载状态和错误处理
defineAsyncComponent
支持配置加载状态和错误处理,通过 loadingComponent
和 errorComponent
参数来实现。
js
import { defineAsyncComponent } from 'vue';
const AsyncComponent = defineAsyncComponent({
loader: () => import('./components/MyComponent.vue'),
loadingComponent: LoadingSpinner, // 加载时显示的组件
errorComponent: ErrorComponent, // 加载失败时显示的组件
delay: 200, // 延迟显示加载组件的时间(毫秒)
timeout: 3000 // 超时时间(毫秒)
});
export default {
components: {
AsyncComponent
}
};
3. 结合 Suspense 使用
Vue 3 引入了 Suspense
组件,可以更好地处理异步组件的加载状态。Suspense
提供了两个插槽:#default
用于渲染异步组件,#fallback
用于显示加载状态。
html
<template>
<Suspense>
<template #default>
<AsyncComponent />
</template>
<template #fallback>
<LoadingSpinner />
</template>
</Suspense>
</template>
<script>
import { defineAsyncComponent } from 'vue';
import LoadingSpinner from './components/LoadingSpinner.vue';
const AsyncComponent = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
);
export default {
components: {
AsyncComponent,
LoadingSpinner
}
};
</script>
4. 动态导入
异步组件通常与动态导入(import()
)结合使用,以实现按需加载。这种方式可以减小初始包体积,提升应用性能。
js
const AsyncComponent = defineAsyncComponent(() =>
import('./components/MyComponent.vue')
);
5. 结合路由使用
在 Vue Router 中,异步组件常用于实现路由懒加载,从而优化应用的加载速度。
js
import { defineAsyncComponent } from 'vue';
const routes = [
{
path: '/about',
component: defineAsyncComponent(() =>
import('./views/AboutView.vue')
)
}
];
6. 高级用法:自定义加载逻辑
如果需要更复杂的加载逻辑,可以在 defineAsyncComponent
的工厂函数中实现。
js
const AsyncComponent = defineAsyncComponent(() => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve(import('./components/MyComponent.vue'));
}, 1000); // 模拟延迟加载
});
});
7. 与 Vue 2 的对比
在 Vue 2 中,异步组件通常通过 Vue.component
或 component
选项来定义,语法较为繁琐:
js
// Vue 2
Vue.component('async-component', () => import('./components/MyComponent.vue'));
8. 封装加载异步组件的工具类
js
// utils/loadAsync.js
import { defineAsyncComponent } from 'vue';
/**
* 动态加载异步组件或页面
* @param {string} path - 组件或页面的路径
* @param {Object} options - 配置项
* @param {Component} options.loadingComponent - 加载时显示的组件
* @param {Component} options.errorComponent - 加载失败时显示的组件
* @param {number} options.delay - 延迟显示加载组件的时间(毫秒)
* @param {number} options.timeout - 超时时间(毫秒)
* @returns {Promise<Component>} - 异步组件或页面
*/
export function loadAsync(path, options = {}) {
return defineAsyncComponent({
loader: () => import(/* @vite-ignore */ `@/${path}`),
loadingComponent: options.loadingComponent || null,
errorComponent: options.errorComponent || null,
delay: options.delay || 200,
timeout: options.timeout || 3000
});
}
Vue 3 的 defineAsyncComponent
提供了更简洁、更灵活的 API,同时支持加载状态和错误处理,提升了开发体验。如果需要更灵活地处理加载状态,可以结合 Suspense
使用。
composition api 相比 options api有什么优势
- 更好的逻辑复用和代码组织
- 更好的类型推导(TS)没有this指向导致的奇怪问题产生 更符合函数式编程规范
vue3 setup作用和原理 以及 scrip setup做了啥事
作用
- 组织逻辑 :
setup
函数是 Vue 3 中用于替代 Vue 2 的data
、methods
、computed
等选项的核心函数,它将组件的逻辑集中在一个地方。 - 响应式数据 :在
setup
函数中,可以使用ref
、reactive
等响应式 API 来创建和管理响应式数据。 - 生命周期钩子 :可以通过
onMounted
、onUpdated
等钩子函数来监听组件的生命周期。 - 暴露数据和方法 :
setup
函数返回的对象中的属性和方法可以直接在模板中使用。
原理
- 执行时机 :
setup
函数在组件实例创建之前执行,因此无法访问this
,因为此时组件实例还未创建。 - 响应式系统 :
setup
函数内部使用 Vue 3 的响应式 API(如ref
、reactive
)来创建响应式数据,这些 API 会追踪数据的变化并自动更新视图。 - 生命周期钩子 :
setup
函数中可以使用生命周期钩子函数(如onMounted
、onUpdated
),这些钩子函数会在相应的生命周期阶段被调用。 - 返回值 :
setup
函数返回一个对象,该对象中的属性和方法会被暴露给模板和组件的其他部分使用。
<script setup>
的作用
<script setup>
是 Vue 3 提供的一种语法糖,用于简化 setup
函数的使用。它允许开发者直接在 <script>
标签中编写 setup
函数的逻辑,而无需显式定义 setup
函数。
做了什么
- 自动暴露变量和方法 :在
<script setup>
中定义的变量和函数会自动暴露给模板,无需通过return
显式返回。 - 简化代码:减少了模板和逻辑之间的代码量,使组件更加简洁。
- 支持顶层
await
:在<script setup>
中可以直接使用await
,方便处理异步逻辑。 - 更好的 TypeScript 支持 :
<script setup>
对 TypeScript 的支持更加友好,类型推断更加准确。
setup
和 <script setup>
的区别
特性 | setup 函数 |
<script setup> |
---|---|---|
代码量 | 需要显式定义 setup 函数并返回对象 |
无需显式定义 setup 函数,代码更简洁 |
暴露变量和方法 | 需要手动 return |
自动暴露 |
顶层 await |
不支持 | 支持 |
TypeScript 支持 | 需要额外配置 | 更友好,类型推断更准确 |
js
export default {
setup(props,context){
// ...
}
}
html
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script setup>
import { ref } from 'vue';
const count = ref(0);
function increment() {
count.value++;
}
</script>
介绍 pinia 以及其持久化最佳实践
Pinia 是 Vue 3 的轻量级状态管理库,它提供了两种风格的使用方式:Options API 风格 和组合式 API 风格。
sh
# 安装
pnpm add pinia
pinia持久化
1. 安装 Pinia 和持久化插件
sh
# 安装
pnpm add pinia pinia-plugin-persistedstate
2. 配置 Pinia 和持久化插件
js
import { createPinia } from 'pinia';
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate';
const pinia = createPinia();
// 是一个官方推荐的插件
pinia.use(piniaPluginPersistedstate);
export default pinia;
3. 定义 Store
-
3.1
state
:定义状态 -
3.2
getters
:定义计算属性 -
3.3
actions
:定义同步和异步方法 -
3.4
persist
:配置持久化
js
import { defineStore } from 'pinia';
export const useUserStore = defineStore('user', {
// 状态(State)
state: () => ({
userInfo: null, // 用户信息
token: '', // 用户 token
isLoading: false, // 加载状态
}),
// 计算属性(Getters)
getters: {
isLoggedIn: (state) => !!state.token, // 是否已登录
userName: (state) => state.userInfo?.name || 'Guest', // 用户名
},
// 方法(Actions)
actions: {
// 同步方法:设置用户信息
setUserInfo(userInfo) {
this.userInfo = userInfo;
},
// 同步方法:设置 token
setToken(token) {
this.token = token;
},
// 异步方法:模拟登录
async login(credentials) {
this.isLoading = true;
try {
// 模拟 API 请求
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
const data = await response.json();
// 更新状态
this.setUserInfo(data.userInfo);
this.setToken(data.token);
} catch (error) {
console.error('Login failed:', error);
} finally {
this.isLoading = false;
}
},
// 异步方法:模拟退出登录
async logout() {
this.isLoading = true;
try {
// 模拟 API 请求
await fetch('/api/logout', { method: 'POST' });
// 清空状态
this.setUserInfo(null);
this.setToken('');
} catch (error) {
console.error('Logout failed:', error);
} finally {
this.isLoading = false;
}
},
},
// 持久化配置
persist: {
enabled: true,
strategies: [
{
key: 'user-store', // 存储的键名
storage: localStorage, // 存储方式
paths: ['token', 'userInfo'], // 只持久化 token 和 userInfo
},
],
},
});
4. 在组件中使用 Store
-
4.1 使用
state
和getters
-
4.2 调用
actions
方法 -
4.3 使用辅助函数(如
mapState
、mapGetters
、mapActions
)
html
<template>
<div>
<p v-if="isLoading">Loading...</p>
<div v-else>
<p>User Name: {{ userName }}</p>
<p>Logged In: {{ isLoggedIn ? 'Yes' : 'No' }}</p>
<button @click="handleLogin">Login</button>
<button @click="handleLogout">Logout</button>
</div>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'pinia';
import { useUserStore } from './stores/user';
export default {
computed: {
// 使用 mapState 辅助函数映射 state
...mapState(useUserStore, ['userInfo', 'token', 'isLoading']),
// 使用 mapGetters 辅助函数映射 getters
...mapGetters(useUserStore, ['isLoggedIn', 'userName']),
},
methods: {
// 使用 mapActions 辅助函数映射 actions
...mapActions(useUserStore, ['login', 'logout']),
// 处理登录
handleLogin() {
this.login({ username: 'admin', password: '123456' });
},
// 处理退出登录
handleLogout() {
this.logout();
},
},
};
</script>
基于 组合式 API 风格 的 Pinia 使用示例
1、2步骤和 options api
一致
3. 定义 Store
-
3.1
state
:定义状态 -
3.2
getters
:定义计算属性 -
3.3
actions
:定义同步和异步方法 -
3.4
persist
:配置持久化
js
import { defineStore } from 'pinia';
import { ref, computed } from 'vue';
export const useUserStore = defineStore('user', () => {
// 状态(State)
const userInfo = ref({ name: 'Guest', age: 18 });
const token = ref('');
const isLoading = ref(false);
// 计算属性(Getters)
const isLoggedIn = computed(() => !!token.value);
const userName = computed(() => userInfo.value.name || 'Guest');
// 方法(Actions)
function setUserInfo(info) {
userInfo.value = info;
}
function setToken(newToken) {
token.value = newToken;
}
async function login(credentials) {
isLoading.value = true;
try {
// 模拟 API 请求
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify(credentials),
});
const data = await response.json();
// 更新状态
setUserInfo(data.userInfo);
setToken(data.token);
} catch (error) {
console.error('Login failed:', error);
} finally {
isLoading.value = false;
}
}
async function logout() {
isLoading.value = true;
try {
// 模拟 API 请求
await fetch('/api/logout', { method: 'POST' });
// 清空状态
setUserInfo({ name: 'Guest', age: 18 });
setToken('');
} catch (error) {
console.error('Logout failed:', error);
} finally {
isLoading.value = false;
}
}
return {
userInfo,
token,
isLoading,
isLoggedIn,
userName,
setUserInfo,
setToken,
login,
logout,
};
}, {
// 持久化配置
persist: {
enabled: true, // 启用持久化
strategies: [
{
key: 'user-store', // 存储的键名
storage: localStorage, // 存储方式 默认为 localStorage
paths: ['token', 'userInfo'], // 存储字段 key
},
],
},
});
4. 在组件中使用 Store
-
4.1 使用
storeToRefs
解构state
和getters
-
4.2 调用
actions
方法 -
4.3 使用
$patch
批量更新状态
html
<template>
<div>
<p v-if="isLoading">Loading...</p>
<div v-else>
<p>User Name: {{ userName }}</p>
<p>Logged In: {{ isLoggedIn ? 'Yes' : 'No' }}</p>
<button @click="handleLogin">Login</button>
<button @click="handleLogout">Logout</button>
<button @click="updateUserInfo">Update User Info</button>
</div>
</div>
</template>
<script setup>
import { useUserStore } from './stores/user';
import { storeToRefs } from 'pinia';
const userStore = useUserStore();
// 使用 storeToRefs 解构 state 和 getters
const { userInfo, token, isLoading, isLoggedIn, userName } = storeToRefs(userStore);
// 直接解构 actions
const { login, logout } = userStore;
// 处理登录
function handleLogin() {
login({ username: 'admin', password: '123456' });
}
// 处理退出登录
function handleLogout() {
logout();
}
// 使用 $patch 批量更新状态
function updateUserInfo() {
userStore.$patch({
userInfo: { name: 'Admin', age: 30 },
token: 'new-token',
});
}
</script>
pinia其他存储方案
手动实现持久化
- 保存状态到本地存储 :在 Store 的
actions
或生命周期钩子中,将状态保存到localStorage
或sessionStorage
。 - 从本地存储恢复状态 :在 Store 初始化时,从
localStorage
或sessionStorage
中读取并恢复状态。
js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: JSON.parse(localStorage.getItem('count')) || 0,
}),
actions: {
increment() {
this.count++;
this.saveState();
},
decrement() {
this.count--;
this.saveState();
},
saveState() {
localStorage.setItem('count', JSON.stringify(this.count));
},
},
});
使用插件 pinia-plugin-persist
pinia-plugin-persist
是另一个流行的持久化插件,功能与 pinia-plugin-persistedstate
类似。
安装
sh
npm install pinia-plugin-persist
配置
在创建 Pinia 实例时,使用该插件。
js
import { createPinia } from 'pinia';
import piniaPluginPersist from 'pinia-plugin-persist';
const pinia = createPinia();
pinia.use(piniaPluginPersist);
export default pinia;
在 Store 中使用
在定义 Store 时,添加 persist
配置。
js
import { defineStore } from 'pinia';
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
},
persist: {
enabled: true, // 启用持久化
strategies: [
{
key: 'counter', // 存储的键名
storage: localStorage, // 存储方式,默认为 localStorage
},
],
},
});
4. 持久化方案对比
方案 | 优点 | 缺点 |
---|---|---|
手动实现持久化 | 无需额外依赖,完全可控 | 代码量较多,容易出错 |
pinia-plugin-persistedstate |
官方推荐,配置简单,功能强大 | 需要额外安装插件 |
pinia-plugin-persist |
功能丰富,支持多种存储方式 | 社区维护,可能不如官方插件稳定 |
ref与reactive的区别源码级别比较
ref
和 reactive
是 Vue 3 中用于创建响应式数据的两个核心 API。
ref
的源码实现
- 核心逻辑
ref
用于将一个基本类型或对象转换为响应式数据。它的核心逻辑是:
-
如果传入的值是对象,则调用
reactive
将其转换为响应式对象。 -
如果传入的值是基本类型,则将其包装为一个包含
value
属性的对象。 -
源码片段
js
function ref(value) {
return createRef(value);
}
function createRef(rawValue, shallow = false) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
class RefImpl<T> {
private _value: T;
private _rawValue: T;
public readonly __v_isRef = true;
constructor(value: T, public readonly _shallow: boolean) {
this._rawValue = _shallow ? value : toRaw(value);
this._value = _shallow ? value : toReactive(value);
}
get value() {
track(this, TrackOpTypes.GET, 'value');
return this._value;
}
set value(newVal) {
if (hasChanged(newVal, this._rawValue)) {
this._rawValue = newVal;
this._value = this._shallow ? newVal : toReactive(newVal);
trigger(this, TriggerOpTypes.SET, 'value', newVal);
}
}
}
关键点
ref
通过RefImpl
类实现,内部维护了一个_value
属性。- 访问
value
属性时,会触发依赖收集(track
)。 - 修改
value
属性时,会触发更新(trigger
)。 - 如果传入的值是对象,
ref
会调用toReactive
,实际上是调用reactive
。
reactive
的源码实现
- 核心逻辑
reactive
用于将一个对象转换为响应式对象。它的核心逻辑是:
-
使用
Proxy
代理对象,拦截对对象的访问和修改操作。 -
通过
track
和trigger
实现依赖收集和更新触发。 -
源码片段
js
function reactive(target) {
if (target && (target as any).__v_isReadonly) {
return target;
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
);
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
if (!isObject(target)) {
return target;
}
if (target.__v_raw && !(isReadonly && target.__v_isReactive)) {
return target;
}
const proxy = new Proxy(target, baseHandlers);
return proxy;
}
关键点
reactive
使用Proxy
代理对象,拦截对象的get
、set
、deleteProperty
等操作。- 在
get
操作中,调用track
进行依赖收集。 - 在
set
操作中,调用trigger
触发更新。 reactive
只能用于对象,不能用于基本类型。
ref
与 reactive
的区别
3.1 数据类型
ref
:可以用于基本类型(如number
、string
)和对象。reactive
:只能用于对象。
3.2 访问方式
ref
:需要通过.value
访问或修改值。reactive
:直接访问或修改对象的属性。
3.3 实现方式
ref
:通过RefImpl
类实现,内部维护一个_value
属性。reactive
:通过Proxy
实现,直接代理对象。
3.4 性能
ref
:对于基本类型,性能更高,因为不需要Proxy
代理。reactive
:对于对象,性能与ref
类似,但更直观。
3.5 使用场景
ref
:适合用于基本类型或需要明确.value
访问的场景。reactive
:适合用于对象,尤其是嵌套对象。
源码级别比较总结
特性 | ref |
reactive |
---|---|---|
数据类型 | 基本类型和对象 | 对象 |
访问方式 | 通过 .value 访问 |
直接访问属性 |
实现方式 | 通过 RefImpl 类实现 |
通过 Proxy 实现 |
性能 | 对于基本类型性能更高 | 对于对象性能与 ref 类似 |
使用场景 | 基本类型或需要 .value 访问的场景 |
对象,尤其是嵌套对象 |
keep-alive 最佳实践
<keep-alive>
是 Vue 3 中用于缓存组件实例的内置组件,可以提升性能。- 适用于动态组件切换、路由切换等场景。
- 通过
include
、exclude
和max
属性可以精确控制缓存行为。 - 结合
activated
和deactivated
生命周期钩子,可以更好地管理组件的状态
缓存动态组件
html
<template>
<keep-alive>
<component :is="currentComponent" />
</keep-alive>
<button @click="toggleComponent">Toggle Component</button>
</template>
<script setup>
import { ref } from 'vue';
import ComponentA from './ComponentA.vue';
import ComponentB from './ComponentB.vue';
const currentComponent = ref('ComponentA');
function toggleComponent() {
currentComponent.value = currentComponent.value === 'ComponentA' ? 'ComponentB' : 'ComponentA';
}
</script>
缓存路由组件
html
<template>
<keep-alive>
<router-view />
</keep-alive>
</template>
注意事项
include
和 exclude
属性依赖于路由组件的 name
属性,因此需要确保路由组件设置了 name
。 对于动态路由(如 /user/:id
),默认情况下,不同参数的路由会共享同一个组件实例。如果需要为每个参数单独缓存,可以使用 key
属性。
html
<template>
<keep-alive>
<router-view :key="$route.fullPath" />
</keep-alive>
</template>
在嵌套路由中,<keep-alive>
只会缓存当前层级的组件。如果需要缓存嵌套的子组件,可以在子路由的 <router-view>
外层也包裹 <keep-alive>
。
缓存过多路由组件会导致内存占用过高,应根据实际需求合理使用 <keep-alive>
。可以通过 max
属性限制缓存数量
当路由组件被缓存或激活时,会触发 activated
和 deactivated
生命周期钩子。可以在这些钩子中执行特定逻辑,如数据加载或清理
- 结合
meta
字段控制缓存
html
<template>
<router-view v-slot="{ Component }">
<keep-alive>
<component :is="Component" v-if="$route.meta.keepAlive" />
</keep-alive>
<component :is="Component" v-if="!$route.meta.keepAlive" />
</router-view>
</template>
- 处理滚动位置
在缓存路由组件时,可能需要保留滚动位置。可以通过 scrollBehavior
配置实现。
js
const router = createRouter({
history: createWebHistory(),
routes,
scrollBehavior(to, from, savedPosition) {
if (savedPosition) {
return savedPosition; // 恢复滚动位置
} else {
return { top: 0 }; // 滚动到顶部
}
},
});
vuex以及最佳实践以及其持久化方案
Vuex 是 Vue.js 的官方状态管理库,用于管理应用中的共享状态。它通过集中式存储管理应用的所有组件的状态,并以可预测的方式更新状态。
Vuex Store 定义 (vue2版本)
js
import Vue from 'vue';
import Vuex from 'vuex';
// 持久化插件
import createPersistedState from 'vuex-persistedstate';
Vue.use(Vuex);
const store = new Vuex.Store({
state: {
count: 0,
user: null,
},
mutations: {
increment(state) {
state.count++;
},
setUser(state, user) {
state.user = user;
},
},
actions: {
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
},
},
plugins: [createPersistedState()],
});
export default store;
组件中使用
html
<template>
<div>
<p>Count: {{ count }}</p>
<p>User: {{ user ? user.name : 'Guest' }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
import { mapState, mapActions } from 'vuex';
export default {
computed: {
...mapState(['count', 'user']),
},
methods: {
...mapActions(['incrementAsync']),
increment() {
this.$store.commit('increment');
},
},
};
</script>
Vue 3 组合式 API 结合 Vuex 的最佳实践以及持久化方案
安装 Vuex
sh
pnpm add vuex@next
创建 Vuex Store
js
import { createStore } from 'vuex';
const store = createStore({
state: {
count: 0,
user: null,
},
mutations: {
// 同步方法
increment(state) {
state.count++;
},
setUser(state, user) {
state.user = user;
},
},
actions: {
// 异步方法
incrementAsync({ commit }) {
setTimeout(() => {
commit('increment');
}, 1000);
},
},
getters: {
doubleCount: (state) => state.count * 2,
},
});
export default store;
在 Vue 应用中使用 Vuex
js
// main.js / main.ts
import { createApp } from 'vue';
import App from './App.vue';
import store from './store';
createApp(App).use(store).mount('#app');
组合式 API 结合 Vuex
使用 useStore
访问 Store
在组合式 API 中,可以使用 useStore
函数访问 Vuex Store。
html
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
import { computed } from 'vue';
import { useStore } from 'vuex';
export default {
setup() {
const store = useStore();
const count = computed(() => store.state.count);
const doubleCount = computed(() => store.getters.doubleCount);
function increment() {
store.commit('increment');
}
function incrementAsync() {
store.dispatch('incrementAsync');
}
return {
count,
doubleCount,
increment,
incrementAsync,
};
},
};
</script
使用 mapState
、mapGetters
、mapActions
辅助函数
在组合式 API 中,可以使用 mapState
、mapGetters
、mapActions
辅助函数简化代码。
html
<template>
<div>
<p>Count: {{ count }}</p>
<p>Double Count: {{ doubleCount }}</p>
<button @click="increment">Increment</button>
<button @click="incrementAsync">Increment Async</button>
</div>
</template>
<script>
import { mapState, mapGetters, mapActions } from 'vuex';
export default {
computed: {
...mapState(['count']),
...mapGetters(['doubleCount']),
},
methods: {
...mapActions(['incrementAsync']),
increment() {
this.$store.commit('increment');
},
},
};
</script>
最佳实践
模块化 将 store
分割成多个模块,便于管理和维护。
js
const userModule = {
state: () => ({ user: null }),
mutations: {
setUser(state, user) {
state.user = user;
},
},
};
const store = createStore({
modules: {
user: userModule,
},
});
使用 namespaced
模块 为模块启用命名空间,避免命名冲突。
js
const userModule = {
namespaced: true,
state: () => ({ user: null }),
mutations: {
setUser(state, user) {
state.user = user;
},
},
};
避免直接修改 state
始终通过 mutations
修改 state
,确保状态变更可追踪。
使用 actions
处理异步操作 将异步逻辑放在 actions
中,保持 mutations
的同步性。
使用 vuex-persistedstate
插件 vuex-persistedstate
是一个流行的 Vuex 持久化插件,可以自动将 state
保存到 localStorage
或 sessionStorage
。
sh
# 安装
pnpm add vuex-persistedstate
配置
js
import createPersistedState from 'vuex-persistedstate';
const store = createStore({
state: { ... },
mutations: { ... },
actions: { ... },
plugins: [createPersistedState()],
});
自定义配置
js
createPersistedState({
key: 'my-app-state', // 存储的键名
storage: window.sessionStorage, // 存储方式
paths: ['user', 'cart'], // 只持久化指定模块
});
使用 vuex-persist
插件
vuex-persist
是另一个 Vuex 持久化插件,支持更多存储方式(如 localForage
)。
sh
# 安装
npm install vuex-persist
配置
js
import VuexPersistence from 'vuex-persist';
const vuexLocal = new VuexPersistence({
storage: window.localStorage,
});
const store = createStore({
state: { ... },
mutations: { ... },
actions: { ... },
plugins: [vuexLocal.plugin],
});
vuex 小结
- 组合式 API 提供了更灵活的方式来组织组件逻辑,结合 Vuex 可以更好地管理应用状态。
- 最佳实践 :模块化、使用辅助函数、避免直接修改
state
、处理异步操作。 - 持久化方案 :使用
vuex-persistedstate
或vuex-persist
插件,确保状态在页面刷新后能够正确恢复。