ref和reactive
ref
定义单个响应式数据
- 数据类型可以是任意类型。它通常用于定义原始数据类型为响应式数据。
- 返回一个响应式对象,该对象包含一个
.value
属性,可用于获取和设置数据。 - 底层采用Object.defineProperty()实现,如果数据类型是对象,会转化为reactive
- 源码:
function ref(value) {
return createRef(value, false);
}
function createRef(rawValue, shallow) {
if (isRef(rawValue)) {
return rawValue;
}
return new RefImpl(rawValue, shallow);
}
class RefImpl {
constructor(value, __v_isShallow) { // __v_isShallow 一般为false
this.__v_isShallow = __v_isShallow;
this.dep = void 0;
this.__v_isRef = true;
this._rawValue = __v_isShallow ? value : toRaw(value);
this._value = __v_isShallow ? value : toReactive(value);
}
get value() {
trackRefValue(this);
return this._value;
}
set value(newVal) {
const useDirectValue = this.__v_isShallow || isShallow(newVal) || isReadonly(newVal);
newVal = useDirectValue ? newVal : toRaw(newVal);
if (shared.hasChanged(newVal, this._rawValue)) {
const oldVal = this._rawValue;
this._rawValue = newVal;
this._value = useDirectValue ? newVal : toReactive(newVal);
triggerRefValue(this, 4, newVal, oldVal);
}
}
}
// 判断是否是对象,是的话转reactive
const toReactive = (value) => shared.isObject(value) ? reactive(value) : value;
reactive
定义多个响应式数据
- 数据类型必须是对象
- 返回一个响应式对象,必须使用响应式对象来获取属性和设置数据
- 底层采用的是new Proxy()
function reactive(target) {
if (isReadonly(target)) {
return target;
}
return createReactiveObject( // 创建reactive对象
target,
false,
mutableHandlers,
mutableCollectionHandlers,
reactiveMap
);
}
function createReactiveObject(target, isReadonly2, baseHandlers, collectionHandlers, proxyMap) {
if (!shared.isObject(target)) {
{
warn(
`value cannot be made ${isReadonly2 ? "readonly" : "reactive"}: ${String(
target
)}`
);
}
return target;
}
if (target["__v_raw"] && !(isReadonly2 && target["__v_isReactive"])) {
return target;
}
const existingProxy = proxyMap.get(target);
if (existingProxy) {
return existingProxy;
}
const targetType = getTargetType(target);
if (targetType === 0 /* INVALID */) {
return target;
}
const proxy = new Proxy(
target,
targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
);
proxyMap.set(target, proxy);
return proxy;
}
computed源码分析
dirty是监听的一个属性,
如果dirty是true的时候,读取computed的值就会重新计算
如果dirty是false的时候,就会使用缓存
const computed = (getterOrOptions, debugOptions) => {
const c = computed$1(getterOrOptions, debugOptions, isInSSRComponentSetup);
{
const i = getCurrentInstance();
if (i && i.appContext.config.warnRecursiveComputed) {
c._warnRecursive = true;
}
}
return c;
};
function computed$1(getterOrOptions, debugOptions, isSSR = false) {
let getter;
let setter;
const onlyGetter = isFunction(getterOrOptions);
if (onlyGetter) {
getter = getterOrOptions;
setter = () => {
warn$2("Write operation failed: computed value is readonly");
} ;
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
const cRef = new ComputedRefImpl(getter, setter, onlyGetter || !setter, isSSR);
if (debugOptions && !isSSR) {
cRef.effect.onTrack = debugOptions.onTrack;
cRef.effect.onTrigger = debugOptions.onTrigger;
}
return cRef;
}
class ComputedRefImpl {
constructor(getter, _setter, isReadonly, isSSR) {
this.getter = getter;
this._setter = _setter;
this.dep = void 0;
this.__v_isRef = true;
this["__v_isReadonly"] = false;
this.effect = new ReactiveEffect(
() => getter(this._value),
() => triggerRefValue(
this,
this.effect._dirtyLevel === 2 ? 2 : 3
)
);
this.effect.computed = this;
this.effect.active = this._cacheable = !isSSR;
this["__v_isReadonly"] = isReadonly;
}
get value() {
const self = toRaw(this);
if ((!self._cacheable || self.effect.dirty) && hasChanged(self._value, self._value = self.effect.run())) {
triggerRefValue(self, 4);
}
trackRefValue(self);
if (self.effect._dirtyLevel >= 2) {
if (this._warnRecursive) {
warn$2(COMPUTED_SIDE_EFFECT_WARN, `
getter: `, this.getter);
}
triggerRefValue(self, 2);
}
return self._value;
}
set value(newValue) {
this._setter(newValue);
}
// #region polyfill _dirty for backward compatibility third party code for Vue <= 3.3.x
get _dirty() {
return this.effect.dirty;
}
set _dirty(v) {
this.effect.dirty = v;
}
// #endregion
}
vue2和vue3的区别
写法
vue2是Options Api,vue3是Composition API
响应式
vue2的响应式原理基于object.defineProperty,而vue3则基于proxy对象(ref使用的也是object.defineProperty,如果是引用对象类型才会转为reactive,而reactive是proxy), object.defineProperty只是对属性进行拦截,会去遍历一个对象的每一个属性去添加getter和setter,如果遇到对属性的删除增加,或者对数组下标进行修改,就拿不到响应式数据(可以使用vue自带的set方法去重新设置响应式)
proxy就代理的是整个对象,proxy针对于对象或者数组都有更多的api,可以深度监听对象的变化,实现对嵌套对象的深度监听
diff算法
vue2的diff算法基于虚拟dom采用双指针算法和同级比较,vue3基于proxy和patch flag,引用的patch flag概念精确判断更新节点,采用proxy对象来跟踪动态节点的变化,提高diff算法的效率
打包体积
Vue.js 2 的打包体积通常比较大,原因在于它内置了很多功能和组件,Vue.js 3 采用了Tree-shaking,更好的组件化支持,更好的模板编译来优化打包体积,使打包体积更小
Tree-Shaking
Tree-Shaking
最先在rollup.js中应用
,后来webpack,vite。简单来说就是移除掉项目中永远不会被执行的代码(dead code
),即代码虽然依赖某个模块,但只使用其中的某些功能,通过Tree-shaking
,将没有使用的模块代码移除掉,削减项目的体积
tree-shaking是依赖于ES6的模块特性,即模块必须是ESM(ES Module),但是vue2是基于es5搭建的
vuex和pinia的区别
设计和使用
- Vuex:采用全局单例模式,通过一个store对象来管理所有状态
- Pinia:采用分离模式,每个组件都有自己的store实例
数据修改
- Vuex:包含mutations和actions,mutations用于同步修改状态,actions用于处理异步操作
- Pinia:没有mutations,只有state、getters和actions1。Pinia的actions可以直接修改状态,而不需要像Vuex那样通过mutations来修改状态。
语法
- Pinia:语法上比Vuex更容易理解和使用,提供了更好的TypeScript支持。Pinia与Vue 3的Composition API紧密结合,允许将状态逻辑作为可复用的函数组合起来。
- Vuex:Vuex的语法和用法相对传统,对于熟悉Vue 2的开发者来说可能更加熟悉
体积和性能
- Pinia:体积较小,约1KB,且性能较好,因为它使用了新的ES6语法和新的数据处理方式。
- Vuex:体积相对较大,但性能稳定可靠,是Vue.js官方提供的状态管理库
keep-alive
keep-alive组件的主要作用就是将需要缓存的组件进行缓存,当组件被切换时,它会将之前缓存的组件重新渲染到页面上,而不会再重新创建新的组件实例。这种缓存机制可以极大地提高页面的加载速度和响应速度
keep-alive组件利用了其中的两个生命周期钩子函数:activated和deactivated
- activated:在activated函数中,keep-alive组件会将之前缓存的组件重新渲染到页面上,而不会重新创建实例。这是因为keep-alive组件使用了LRU(Least Recently Used)算法来管理缓存的组件实例,当缓存的组件数量超过一定的阈值时,较早使用的组件会被销毁,释放内存空间。
- deactivated:在deactivated函数中,keep-alive组件会将当前的组件实例保存到缓存中,不会被销毁。这样当组件再次被激活时,可以直接从缓存中取出组件实例,而不需要重新创建。
effect
响应式API,用于追踪响应式依赖项
import { reactive, effect } from 'vue';
const state = reactive({
count: 0,
});
// 创建一个 effect 函数
const countEffect = effect(() => {
console.log(`Count is: ${state.count}`);
});
// 修改状态
state.count++; // 触发 countEffect 函数重新执行
// 输出:Count is: 1
diff算法
diff算法就是进行虚拟节点对比,并返回一个patch对象,用来存储两个节点不同的地方,最后用patch记录的消息去局部更新Dom
- 只会同级比较,不会跨层级比较
- 在diff比较的过程中,循环从两边向中间比较
diff 算法的在很多场景下都有应用,在 vue 中,作用于虚拟 dom 渲染成真实 dom 的新旧 VNode 节点比较
vue2和vue3diff算法的不同
1.Virtual DOM的优化
Vue 2 中的 diff 算法针对整个 Virtual DOM 树进行了完整的比较,导致在大型应用中可能存在性能问题。
Vue 3 中通过静态分析和标记,将组件标记为静态、动态或稳定,从而避免不必要的 Virtual DOM 比较,提高了渲染性能。
2.动态指令的优化
Vue 2 中动态指令的 diff 算法在某些情况下效率不高,可能会导致不必要的重新渲染。
Vue 3 中通过优化动态指令的处理方式,提高了动态指令的 diff 效率,减少了不必要的更新操作,提升了性能。
3.事件侦听器的优化
在 Vue 2 中,每次更新都会重新设置事件侦听器,存在一定性能损耗。
Vue 3 中通过事件侦听器的缓存和重用,减少了事件侦听器的重复创建和销毁,提高了事件的处理效率。
4.静态树的处理
Vue 2 中没有对静态树(即不会发生变化的部分)做特殊处理,仍然会进行完整的 diff 操作。
Vue 3 中对静态树进行了优化处理,避免了不必要的比较和更新,提高了整体渲染性能。
5.Fragments的处理
在 Vue 2 中,使用 Fragments 时会引入额外的 Virtual DOM 节点,导致在 diff 过程中产生额外的开销。
Vue 3 中通过优化 Fragments 的处理方式,减少了额外节点的创建和比较,提高了对 Fragments 的 diff 效率。