vue2与vue3的区别
- 响应式系统
(1)Vue2基于 Object.defineProperty实现,会在初始化时,把 data 中的每个属性用 Object.defineProperty 转换成 getter / setter,当属性被访问时触发 getter,依赖收集;当属性被修改时触发 setter,派发更新。
缺点:对象新增/删除属性时,要用vue.delete;通过索引直接修改数组元素,无法触发更新;Vue 包装过的方法(push、splice)才能响应式;初始化时会递归遍历整个对象,数据量大时性能很差。
(2)Vue3使用 ES6 Proxy 来拦截对象的操作,响应式转换在“访问时”才进行,而不是 Vue2 的“初始化时”。
- Options API 与 Composition API
Options API(选项式 API)使用 data、methods、computed、watch、mounted 等不同的选项组织代码。
Composition API 在 setup() 函数里,使用 ref、reactive、computed、watch 等组合功能。
- Diff 算法
(1) 静态节点提升(Static Hoisting)
- 原理:编译时识别出完全静态的节点(如纯文本、无动态绑定的标签),将其标记为静态节点,并在编译阶段就从渲染函数中“提升”出去,只创建一次。
- 效果:后续更新时,diff 算法会直接跳过这些静态节点,无需对比,减少比对次数。
例: <div >静态文本</div > 这类节点不会参与每次更新的 diff 过程。
(2) 补丁标记(Patch Flags)
- 原理:编译时为动态节点添加“补丁标记”,标记该节点的动态内容类型(如动态文本、动态 class、动态 props 等)。
- 效果:diff 时,只需要根据标记检查节点的特定动态部分,无需对比整个节点的所有属性。
例: <div :class="activeClass" >内容</div > 会被标记为“动态 class”,更新时仅检查 class 是否变化,忽略其他静态内容。
(3) 列表 diff 优化(稳定序列复用)
- 针对场景: v-for 渲染的列表更新时,Vue2 的 diff 算法可能会对整个列表进行全量对比,效率较低。
- Vue3 优化:
- 当列表项的顺序变化但部分节点结构未变时,通过最长递增子序列算法,找出可以复用的稳定节点序列,减少节点的移动和重新创建。
- 配合 key ,更精准地定位节点的增删改位置,避免不必要的节点操作。
(4) 减少比对层级(Tree Flattening)
- 原理:Vue2 的 diff 是“全树递归比对”,即使父节点无变化,也会递归比对所有子节点。
- Vue3 优化:编译时将模板结构“扁平化”,将嵌套的子节点转换为一个线性数组,diff 时通过数组索引直接访问节点,减少递归层级,提升遍历效率。
- Fragments
-
Vue2:一个组件只能有一个根节点。
-
Vue3:支持返回多个根节点(Fragments),减少无意义的包裹
div。
- 生命周期
setup 替代了 beforeCreate / created。
-
创建阶段:
beforeCreate→created -
挂载阶段:
beforeMount→mounted -
更新阶段:
beforeUpdate→updated -
销毁阶段:
beforeDestroy→destroyed
vuex
核心概念
State(状态) Vuex 使用单一状态树,用一个对象包含全部的应用层级状态。每个应用仅包含一个 store 实例。
Getters(获取器) Vuex 允许在 store 中定义 getters,可以认为是 store 的计算属性。getters 的返回值会根据它的依赖被缓存起来。
Mutations(变更) 更改 Vuex store 中状态的唯一方法是提交 mutation。mutation 必须是同步函数,每个 mutation 都有一个字符串的事件类型和一个回调函数。
this.$store.commit('DECREMENT')
Actions(动作) Action 类似于 mutation,不同之处在于:Action 提交的是 mutation,而不是直接变更状态;Action 可以包含任意异步操作。
this.$store.dispatch('increment')
Modules(模块) Vuex 允许将 store 分割成模块,每个模块拥有自己的 state、mutation、action、getter。
computed: {
...mapState(['count']),
...mapGetters(['doubleCount'])
},
methods: {
...mapActions(['increment', 'decrement']),
...mapMutations(['INCREMENT']),
}
数据流
Vuex 的数据流是单向的:组件触发 Action → Action 提交 Mutation → Mutation 改变 State → State 变化触发视图更新。
主要特点
集中式管理:所有状态存储在一个地方,便于管理和调试。
可预测性:状态变化遵循严格的规则,使得状态变化可预测。
响应式:基于 Vue 的响应式系统,状态变化自动触发视图更新。
开发工具支持:配合 Vue DevTools 提供强大的调试功能,支持时间旅行调试。
适用场景
Vuex 适合中大型单页应用,特别是需要多个组件共享状态或状态变化逻辑复杂的场景。对于简单应用,可能会显得过于复杂,此时可以考虑使用简单的状态管理方案或 Vue 3 的 Composition API。
pinia
核心特性
简洁的 API Pinia 提供了更直观的 API,去除了 Vuex 中的 mutations 概念,actions 可以直接修改状态,使代码更简洁。
完整的 TypeScript 支持 从设计之初就考虑了 TypeScript,提供了完整的类型推导和类型安全,无需额外的类型声明。
模块化设计 每个 store 都是独立的,不需要嵌套结构,支持代码分割和懒加载。
Composition API 友好 与 Vue 3 的 Composition API 完美集成,支持在 setup 函数中直接使用。
轻量级 体积更小,运行时开销更低,性能更好。
主要概念
Store(存储) Pinia 中的 store 是一个保存状态和业务逻辑的实体,类似于组件。每个 store 都有 state、getters 和 actions。
State(状态) 存储数据的地方,是响应式的。可以通过 store 实例直接访问和修改。
Getters(获取器) 类似于计算属性,用于从 state 中派生出一些状态。getters 会被缓存,只有当依赖发生变化时才会重新计算。
Actions(动作) 用于定义业务逻辑,可以是同步或异步的。actions 可以直接修改状态,也可以调用其他 actions。
使用场景
定义一个 Store(store/counter.js)
// store/counter.js 或 counter.ts
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => ({
count: 0,
name: 'My Counter',
}),
getters: {
doubleCount: (state) => state.count * 2,
},
actions: {
increment() {
this.count++
},
reset() {
this.count = 0
},
},
})
在组件中使用 Store(如:App.vue)
<template>
<div>
<h1>{{ counter.name }}</h1>
<p>Count: {{ counter.count }}</p>
<p>Double: {{ counter.doubleCount }}</p>
<button @click="counter.increment">+</button>
<button @click="counter.reset">Reset</button>
</div>
</template>
<script setup>
import { useCounterStore } from './store/counter'
const counter = useCounterStore()
</script>
vuex与pinia对比
架构设计
Vuex 采用单一状态树模式,所有状态集中在一个 store 中,通过 modules 来组织代码,有严格的 mutations/actions 分离。
Pinia 采用多 store 模式,每个 store 都是独立的,更接近组合式 API 的设计理念。它没有 mutations 概念,actions 可以直接修改状态。
语法复杂度
Vuex 的语法相对复杂:
- 需要定义 state、mutations、actions、getters
- 修改状态必须通过 mutations
- 异步操作需要在 actions 中处理
- 大量的样板代码
Pinia 的语法更简洁:
- 只需要定义 state、actions、getters
- 可以直接修改状态或通过 actions
- 支持组合式 API 风格的写法
- 更少的样板代码
TypeScript 支持
Vuex 的 TypeScript 支持比较复杂,需要额外的类型定义和配置。
Pinia 原生支持 TypeScript,类型推断更好,开发体验更佳。
开发工具
Vuex 有成熟的 Vue DevTools 支持。
Pinia 也有很好的 DevTools 支持,并且提供了更直观的调试体验。
性能
Pinia 在性能上有优势,支持代码分割,只有使用到的 store 才会被加载。而 Vuex 需要在应用启动时加载整个状态树。
Vuex原理
1. 核心实现原理
1.1 响应式系统
Vuex利用Vue的响应式系统来实现状态的响应式更新:
// 核心响应式实现
class Store {
constructor(options = {}) {
// 使用Vue实例来实现响应式,利用Vue的响应式系统
// 将state包装在Vue实例的data中,使其具备响应式能力
this._vm = new Vue({
data: {
// 使用$开头表示这是内部属性,避免与用户数据冲突
$state: options.state
}
})
}
// 通过getter暴露state,确保获取的是响应式的状态
get state() {
return this._vm._data.$state
}
// 禁止直接设置state,强制通过mutation来修改状态
set state(v) {
console.error('不能直接设置store的state')
}
}
1.2 安装机制
通过Vue.mixin在所有组件中注入$store:
/**
* Vuex安装函数 - 通过Vue.mixin在所有组件中注入$store
* @param {Vue} Vue - Vue构造函数
*/
function install(Vue) {
// 通过mixin的方式,在每个组件实例化前注入$store
Vue.mixin({
beforeCreate() {
const options = this.$options
// 根组件:直接从options中获取store实例
if (options.store) {
this.$store = options.store
}
// 子组件:从父组件继承$store
else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
})
}
1.3 Mutation实现
class Store {
constructor(options) {
// 创建空的mutations对象来存储所有mutation函数
this._mutations = Object.create(null)
// 注册mutations - 将用户定义的mutations包装成可执行的函数
const mutations = options.mutations || {}
Object.keys(mutations).forEach(key => {
// 包装mutation函数,确保传入正确的参数(state和payload)
this._mutations[key] = (payload) => {
// 调用用户定义的mutation,传入当前状态和载荷
mutations[key].call(this, this.state, payload)
}
})
}
/**
* 提交mutation - 同步修改状态的唯一方式
* @param {string} type - mutation的类型
* @param {any} payload - 传递给mutation的数据
*/
commit(type, payload) {
// 根据类型获取对应的mutation函数
const mutation = this._mutations[type]
if (!mutation) {
console.error(`未知的mutation类型: ${type}`)
return
}
// 执行mutation,修改状态
mutation(payload)
}
}
1.4 Action实现
class Store {
constructor(options) {
// 创建空的actions对象来存储所有action函数
this._actions = Object.create(null)
// 注册actions - 处理异步操作的地方
const actions = options.actions || {}
Object.keys(actions).forEach(key => {
// 包装action函数,提供context对象和payload参数
this._actions[key] = (payload) => {
// action函数接收context对象,包含commit、dispatch、state、getters
return actions[key].call(this, {
commit: this.commit.bind(this), // 用于提交mutation
dispatch: this.dispatch.bind(this), // 用于分发其他action
state: this.state, // 当前状态
getters: this.getters // 所有getters
}, payload)
}
})
}
/**
* 分发action - 处理异步操作
* @param {string} type - action的类型
* @param {any} payload - 传递给action的数据
* @return {Promise} 返回Promise以支持异步操作
*/
dispatch(type, payload) {
// 根据类型获取对应的action函数
const action = this._actions[type]
if (!action) {
console.error(`未知的action类型: ${type}`)
return
}
// 执行action,可能包含异步操作
return action(payload)
}
}
1.5 Getter实现
class Store {
constructor(options) {
// 初始化getters对象
this.getters = {}
const getters = options.getters || {}
// 为每个getter创建属性描述符,实现计算属性的效果
Object.keys(getters).forEach(key => {
// 使用Object.defineProperty定义getter属性
Object.defineProperty(this.getters, key, {
// 每次访问时都会调用这个get函数
get: () => {
// 调用用户定义的getter函数,传入state和getters作为参数
return getters[key](this.state, this.getters)
}
})
})
}
}
2. 完整的简化版实现
/**
* 简化版Vuex Store实现
* 包含完整的state管理、mutations、actions、getters功能
*/
class VuexStore {
constructor(options = {}) {
// 使用Vue实例实现响应式状态管理
// Vue的响应式系统会自动追踪状态变化并更新相关视图
this._vm = new Vue({
data: {
// 使用$前缀避免与用户数据冲突,这是Vue的内部约定
$state: options.state || {}
}
})
// 初始化存储对象
this._mutations = Object.create(null) // 存储所有mutation函数
this._actions = Object.create(null) // 存储所有action函数
this.getters = {} // 存储所有getter
// 注册mutations - 同步修改状态的唯一方式
const mutations = options.mutations || {}
Object.keys(mutations).forEach(key => {
this._mutations[key] = (payload) => {
// 确保mutation函数的this指向store实例
// 传入当前状态和载荷数据
mutations[key].call(this, this.state, payload)
}
})
// 注册actions - 处理异步逻辑和复杂业务逻辑
const actions = options.actions || {}
Object.keys(actions).forEach(key => {
this._actions[key] = (payload) => {
// 构建context对象,提供action所需的所有API
const context = {
commit: this.commit.bind(this), // 提交mutation
dispatch: this.dispatch.bind(this), // 分发其他action
state: this.state, // 当前状态(只读)
getters: this.getters // 所有getter(只读)
}
// 调用用户定义的action函数
return actions[key].call(this, context, payload)
}
})
// 注册getters - 从state派生出一些状态(类似计算属性)
const getters = options.getters || {}
Object.keys(getters).forEach(key => {
// 使用属性描述符定义getter,实现懒计算和缓存
Object.defineProperty(this.getters, key, {
get: () => getters[key](this.state, this.getters),
enumerable: true // 可枚举,方便调试和遍历
})
})
// 绑定方法的this上下文,防止在组件中解构使用时丢失this
this.commit = this.commit.bind(this)
this.dispatch = this.dispatch.bind(this)
}
// 状态的getter,提供对响应式状态的访问
get state() {
return this._vm._data.$state
}
/**
* 提交mutation - 修改状态的唯一合法方式
* @param {string} type - mutation类型
* @param {any} payload - 传递的数据
*/
commit(type, payload) {
const mutation = this._mutations[type]
if (!mutation) {
console.error(`[vuex] unknown mutation type: ${type}`)
return
}
// 执行mutation,同步修改状态
mutation(payload)
}
/**
* 分发action - 处理异步操作和业务逻辑
* @param {string} type - action类型
* @param {any} payload - 传递的数据
* @return {Promise} 始终返回Promise以保证一致的异步接口
*/
dispatch(type, payload) {
const action = this._actions[type]
if (!action) {
console.error(`[vuex] unknown action type: ${type}`)
return Promise.resolve()
}
// 将结果包装为Promise,确保返回值的一致性
return Promise.resolve(action(payload))
}
}
/**
* Vuex安装函数 - 将Vuex集成到Vue应用中
* @param {Vue} Vue - Vue构造函数
*/
function install(Vue) {
// 防止重复安装
if (install.installed) return
install.installed = true
// 通过全局mixin在所有组件中注入$store属性
Vue.mixin({
beforeCreate() {
const options = this.$options
// 根组件:从选项中获取store
if (options.store) {
this.$store = typeof options.store === 'function'
? options.store() // 支持工厂函数
: options.store
}
// 子组件:从父组件继承store
else if (options.parent && options.parent.$store) {
this.$store = options.parent.$store
}
}
})
}
// 导出Vuex对象,包含Store类和install方法
const Vuex = {
Store: VuexStore,
install
}
3. 关键设计思想
3.1 单向数据流
- View -> Action -> Mutation -> State -> View
- 确保状态变更的可追踪性和可预测性
3.2 响应式更新
- 利用Vue的响应式系统
- 状态变更自动触发视图更新
3.3 严格的状态管理
- 只能通过mutation改变状态
- Action处理异步操作
- 开发工具可追踪所有状态变更