이 글은 vue3에서 가장 중요한 API인 ref와 반응형을 이해하는 데 도움이 될 것입니다. 아직도 어떤 것을 사용해야 할지 헷갈리시나요? Vue3를 잘 활용하고 싶다면 곧 vue2의 반응성을 살펴보세요.
Object.defineProperty 메소드를 통해 객체의 getter
및 setter
를 하이재킹하고 getter
에서 종속성을 수집하고 에서 종속성을 트리거합니다. setter
이지만 이 방법에는 몇 가지 단점이 있습니다.
getter
和setter
,在getter
中收集依赖,在setter
中触发依赖,但是这种方式存在一些缺点:由于是遍历递归监听属性,当属性过多或嵌套层级过深时会影响性能
无法监听对象新增的属性和删除属性,只能监听对象本身存在的属性,所以设计了$set
和$delete
如果监听数组的话,无法监听数组元素的增减,只能监听通过下标可以访问到的数组中已有的属性,由于使用Object.defineProperty
遍历监听数组原有元素过于消耗性能,vue放弃使用Object.defineProperty
监听数组,而采用了重写数组原型方法的方式来监听对数组数据的操作,并用$set
和splice
方法来更新数组,$set
和splice
객체의 새 속성과 삭제된 속성은 모니터링할 수 없습니다
$를 설계했습니다. set
및 $delete
배열을 모니터링하면 배열 요소의 증가 또는 감소를 모니터링할 수 없습니다. 배열의 기존 속성만 모니터링할 수 있습니다. Object.defineProperty
순회 모니터링을 통해 액세스할 수 있습니다. Vue는 모니터링을 위해 Object.defineProperty
사용을 포기했습니다. 배열 데이터의 동작을 모니터링하기 위해 배열 프로토타입 메서드를 재정의하는 방법을 채택하고 $set
및 splice
메서드를 사용하여 배열을 업데이트했습니다. >$set 및 splice
는 재정의된 배열 메서드를 호출합니다.
프록시 객체
새로운 객체인 Object.defineProperty의 단점을 겨냥 - ES6에서 프록시가 도입되었습니다(객체 프록시 ) 프록시 개체: 는 개체에 대한 프록시를 만드는 데 사용되며 주로 개체의 일부 기본 동작을 변경하는 데 사용됩니다. 프록시는 대상 개체 앞에 "가로채기" 계층을 설정하는 것으로 이해될 수 있습니다. 모든 액세스는 먼저 이 차단 계층을 통과해야 하므로 외부 액세스를 필터링하고 다시 작성할 수 있는 메커니즘을 제공해야 합니다. 기본 구문은 다음과 같습니다.중단할 수 있는 연산은 다음과 같습니다. | |
---|---|
값 읽기 | |
값 쓰기 | |
in 연산자 | |
Object.getPrototypeOf() | |
Object.getPrototypeOf () | |
Object.setPrototypeOf ( ) | |
Object.isExtensible() | |
Object.preventExtensions() | |
Object.getOwnProperty 설명자() | |
Object.defineProperty | |
Object.keys() Object.getOwnPropertyNames() 및 Object.getOwnPropertySymbols() | |
함수 호출 |
那么使用Proxy
可以解决Vue2
中的哪些问题,总结一下:
var target = { a:1, b:{ c:2, d:{e:3} } } var handler = { get:function(target, prop, receiver){ console.log('触发get:',prop) return Reflect.get(target,prop) }, set:function(target,key,value,receiver){ console.log('触发set:',key,value) return Reflect.set(target,key,value,receiver) } } var proxy = new Proxy(target,handler) proxy.b.d.e = 4 // 输出 触发get:b , 由此可见Proxy仅代理了对象外层属性。
以上写法只代理了对象的外层属性,所以要想深层代理整个对象的所有属性,需要进行递归处理:
var target = { a:1, b:{ c:2, d:{e:3} }, f: {z: 3} } var handler = { get:function(target, prop, receiver){ var val = Reflect.get(target,prop) console.log('触发get:',prop) if(val !== null && typeof val==='object') { return new Proxy(val,handler) // 代理内层属性 } return Reflect.get(target,prop) }, set:function(target,key,value,receiver){ console.log('触发set:',key,value) return Reflect.set(target,key,value,receiver) } } var proxy = new Proxy(target,handler) proxy.b.d.e = 4 // 输出 触发get b,get d, get e
从递归代理可以看出,如果要代理对象的内部属性,Proxy可以只在属性被调用时去设置代理(惰性),访问了e
,就仅递归代理b
下面的属性,不会额外代理其他没有用到的深层属性,如z
。
Reflect
的作用和意义Function.prototype.apply Object.defineProperty
修改某些Object
方法的返回结果,让其变得更合理。比如,Object.defineProperty(obj, name, desc)
在无法定义属性时,会抛出一个错误,而Reflect.defineProperty(obj, name, desc)
则会返回false
。
让Object
操作都变成函数行为。某些Object
操作是命令式,比如name in obj
和delete obj[name]
,而Reflect.has(obj, name)
和Reflect.deleteProperty(obj, name)
让它们变成了函数行为。
Reflect
对象的方法与Proxy
对象的方法一一对应,只要是Proxy
对象的方法,就能在Reflect
对象上找到对应的方法。这就让Proxy
对象可以方便地调用对应的Reflect
方法,完成默认行为,作为修改行为的基础。也就是说,不管Proxy
怎么修改默认行为,你总可以在Reflect
上获取默认行为。
reative
和ref
Vue3 的 reactive 和 ref 正是借助了Proxy来实现。
作用:创建原始对象的响应式副本,即将「引用类型」数据转换为「响应式」数据
参数: reactive参数必须是对象或数组
reative函数实现:
// 判断是否为对象 const isObject = val => val !== null && typeof val === 'object'; // 判断key是否存在 const hasOwn = (target, key) => Object.prototype.hasOwnProperty.call(target, key); export function reactive(target) { // 首先先判断是否为对象 if (!isObject(target)) return target; const handler = { get(target, key, receiver) { console.log(`获取对象属性${key}值`) // 收集依赖 ... const result = Reflect.get(target, key, receiver) // 深度监听(惰性) if (isObject(result)) { return reactive(result); } return result; }, set(target, key, value, receiver) { console.log(`设置对象属性${key}值`) // 首先先获取旧值 const oldValue = Reflect.get(target, key, reactive) let result = Reflect.set(target, key, value, receiver); if (result && oldValue !== value) { // 更新操作 ... } return result }, deleteProperty(target, key) { console.log(`删除对象属性${key}值`) // 先判断是否有key const hadKey = hasOwn(target, key) const result = Reflect.deleteProperty(target, key) if (hadKey && result) { // 更新操作 ... } return result }, // 其他方法 // ... } return new Proxy(target, handler) } const obj = { a: { b: { c: 6 } } }; const proxy = reactive(obj); proxy.a.b.c = 77; // 获取对象属性a值 // 获取对象属性b值 // 设置对象属性c值 77
至此,引用类型的对象我们已经可以把它转化成响应式对象了,Proxy对象只能代理引用类型的对象,对于基本数据类型如何实现响应式呢?
vue的解决方法是把基本数据类型变成一个对象:这个对象只有一个value属性,value属性的值就等于这个基本数据类型的值。然后,就可以用reative
方法将这个对象,变成响应式的Proxy对象。
实际上就是: ref(0) --> reactive( { value:0 })
作用:把基本类型的数据变为响应式数据。
参数:
1.基本数据类型
2.引用类型
3.DOM的ref属性值
export function ref(value?: unknown) { return createRef(value, false) } function createRef(rawValue: unknown, shallow: boolean) { if (isRef(rawValue)) { return rawValue } return new RefImpl(rawValue, shallow) } class RefImpl<T> { private _value: T private _rawValue: T public dep?: Dep = undefined public readonly __v_isRef = true constructor(value: T, public readonly __v_isShallow: boolean) { 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 (hasChanged(newVal, this._rawValue)) { this._rawValue = newVal this._value = useDirectValue ? newVal : toReactive(newVal) triggerRefValue(this, newVal) } } }
大体思路就是,调用ref函数时会new 一个类,这个类监听了value属性的 get 和 set ,实现了在get中收集依赖,在set中触发依赖,而如果需要对传入参数深层监听的话,就会调用我们上面提到的reactive
方法。
即:
ref(0); // 通过监听对象(类)的value属性实现响应式 ref({a: 6}); // 调用reactive方法对对象进行深度监听
根据上面的思路我们可以自己来简单实现下:
// 自定义ref function ref(target) { const result = { // 这里在源码中体现为一个类 RefImpl _value: reactive(target), // target传给reactive方法做响应式处理,如果是对象的话就变成响应式 get value () { return this._value }, set value (val) { this._value = val console.log('set value 数据已更新, 去更新界面') } } return result } // 测试 const ref = ref(9); ref.value = 6; const ref = ref({a: 4}); ref.value.a = 6;
ref 方法包装的数据,需要使用.value
来访问,但在模板中不需要,Vue解析时会自动添加。
reactive
将引用类型值变为响应式,使用Proxy
实现ref
可将基本类型和引用类型都变成响应式,通过监听类的value属性的get
和set
实现,但是当传入的值为引用类型时实际上内部还是使用reactive
方法进行的处理ref
,引用类型使用 reactive
위 내용은 원리에 대한 자세한 설명: Vue3의 반응성과 참조의 차이점의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!