1. reactive和ref的区别
reactive和ref都是vue3中提供的响应式API,用于定义响应式数据的reactive通常用于定义对象数据类型,其本质是基于 Proxy 实现对象代理,所以reactive不能用于定义基本类型数据ref通常是用于定义基本数据类型,其本质是基于 Object.defineProperty() 重新定义属性的方式实现,vue3源码中是基于类的属性访问器实现(本质也是 defineProperty )
2. Ref
2.1 实现Ref和shallowRef
ref的本质是基于类的属性访问器实现的,可以将一个基本类型值进行包装
craeteRef将普通类型变成一个对象类型ref值也可以是对象,但是一般情况下是对象直接使用reactive更合理
RefImpl 类- beta版本之前版本ref是个对象,由于对象不方便拓展,所以改成了类
- 如果传递
shallow = true则只是代理最外层
get value 属性访问器track收集依赖- 代理
value会帮我们代理到_value上
set value 属性设置器- 判断
新值和老值是否改变,改变就更新值 trigger触发依赖执行
- 判断
// packages/reactivity/ref.ts
import { hasChanged, isObject } from "@vue/shared";
import { track, trigger } from "./effect";
import { TrackOpTypes, TriggerOpTypes } from "./operations";
import { reactive } from "./reactive";
export function ref(value) { // value 是一个基本数据类型
// 将 普通类型 变成一个 对象类型
return createRef(value);
}
export function shallowRef(value) { // shallowRef Api
return createRef(value, true);
}
function createRef(rawValue, shallow = false) {
return new RefImpl(rawValue, shallow)
}
const convert = (val) => isObject(val) ? reactive(val) : val; // 递归响应式
class RefImpl {
private _value;
public readonly __v_isRef = true; // 产生的实例会被添加 __v_isRef 表示是一个 ref 属性
constructor(private _rawValue, public readonly _shallow) {
// 如果是深度,需要把里面的都变成响应式的
this._value = _shallow ? _rawValue : convert(_rawValue)
}
// 类的属性访问器
get value() { // 代理 去value 会帮我们代理到 _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 : convert(newVal);
trigger(this, TriggerOpTypes.SET, 'value', newVal);
}
}
}
2.2 实现toRefs
将对象(数组)中的属性转换成
ref属性,实现批量转化
class ObjectRefImpl{
public readonly __v_isRef = true
constructor(private readonly _object, private readonly _key) {}
get value(){
return this._object[this._key]
}
set value(newVal){
this._object[this._key] = newVal
}
}
export function toRef(object,key){
return new ObjectRefImpl(object,key);
}
export function toRefs(object) { // object可能是数组或对象
const ret = isArray(object) ? new Array(object.length) : {};
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret;
}
3. Computed实现原理
vue3.0 中的
computed实现思路和 vue2.0 源码基本一致,也是基于缓存来实现的
// packages/reactivity/ref.ts
import { effect, track, trigger } from './effect';
import { isFunction } from '@vue/shared';
class ComputedRefImpl {
private _value;
private _dirty = true; // 默认是脏值,不要用缓存的值
public readonly effect;
public readonly __v_isRef = true;
constructor(getter, private readonly _setter) {
this.effect = effect(getter, {
lazy: true, // 计算属性特性,默认不执行
scheduler: () => {
if (!this._dirty) { // 依赖属性变化时
this._dirty = true; // 标记为脏值,触发视图更新
trigger(this, 'set', 'value');
}
}
})
}
get value() { // 计算属性也要收集依赖 计算属性本身就是一个effect
if (this._dirty) {
// 取值时执行effect
this._value = this.effect();
this._dirty = false;
}
track(this, TrackOpTypes.GET ,'value'); // 进行属性依赖收集
return this._value
}
set value(newValue) {
this._setter(newValue);
}
}
// vue2 和 vue3 的computed原理是不一样的
export function computed(getterOrOptions) {
let getter;
let setter;
if (isFunction(getterOrOptions)) { // computed两种写法
getter = getterOrOptions;
setter = () => {
console.warn('computed value is readonly')
}
} else {
getter = getterOrOptions.get;
setter = getterOrOptions.set;
}
return new ComputedRefImpl(getter, setter)
}