vue3响应式reactive的实现原理;proxy深层代理;vue3响应式api
参考链接: https://segmentfault.com/a/1190000039194351
我们已经知道vue3的响应式实现从defineProperty变成了proxy
defineProperty有个弊端,只能监听已有属性的变化,新增属性就监听不到,vue2时,需要配合Vue.set来把新增的属性变成响应式;
defineProperty实现响应式:
const obj1 = {}; Object.defineProperty(obj1, 'a', { get() { console.log('get1'); }, set() { console.log('set1'); } }); obj1.b = 2; // 监听不到
proxy实现响应式
const o = { name: 'tom', info: { age: 18 } } const po = new Proxy(o, { get(target, key, proxy) { console.log(`get key:${key}`); return Reflect.get(...arguments); }, set(target, key, value, proxy){ console.log(`set key:${key}, value: ${value}`); return Reflect.set(...arguments); } })po.name // get key:name po.info.age // get key:info
上面就是proxy实现响应式的基本原理
但是有个问题,上面的代码只是潜监听,也就是只监听到了对象第一层属性;
下面是vue3中reacttive源码精简后的代码,看看是如何深层监听
// 工具方法:判断是否是一个对象(注:typeof 数组 也等于 'object' const isObject = val => val !== null && typeof val === 'object'; // 工具方法:值是否改变,改变才触发更新 const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue); // 工具方法:判断当前的 key 是否是已经存在的 const hasOwn = (val, key) => hasOwnProperty.call(val, key); // 闭包:生成一个 get 方法 function createGetter() { return function get(target, key, receiver) { const res = Reflect.get(target, key, receiver); console.log(`getting key:${key}`); // track(target, 'get' /* GET */, key); // 深层代理对象的关键!!!判断这个属性是否是一个对象,是的话继续代理动作,使对象内部的值可追踪 if (isObject(res)) { return reactive(res); } return res; }; } // 闭包:生成一个 set 方法 function createSetter() { return function set(target, key, value, receiver) { const oldValue = target[key]; const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); // 判断当前 key 是否已经存在,不存在的话表示为新增的 key ,后续 Vue “标记”新的值使它其成为响应式 if (!hadKey) { console.log(`add key:${key},value:${value}`); // trigger(target, 'add' /* ADD */, key, value); } else if (hasChanged(value, oldValue)) { console.log(`set key:${key},value:${value}`); // trigger(target, 'set' /* SET */, key, value, oldValue); } return result; }; } const get = createGetter(); const set = createSetter(); // 基础的处理器对象 const mutableHandlers = { get, set // deleteProperty }; // 暴露出去的方法,reactive function reactive(target) { return createReactiveObject(target, mutableHandlers); } // 创建一个响应式对象 function createReactiveObject(target, baseHandlers) { const proxy = new Proxy(target, baseHandlers); return proxy; } const proxyObj = reactive({ id: 1, name: 'front-refined', childObj: { hobby: 'coding' } }); proxyObj.childObj.hobby // get key:childObj // get key:hobby proxyObj.childObj.hobby="play" // get key:childObj // set key:hobby,value:play
这样就监听到更深层的属性变化了
ref实现:
// 工具方法:值是否改变,改变才触发更新 const hasChanged = (value, oldValue) => value !== oldValue && (value === value || oldValue === oldValue); // 工具方法:判断是否是一个对象(注:typeof 数组 也等于 'object' const isObject = val => val !== null && typeof val === 'object'; // 工具方法:判断传入的值是否是一个对象,是的话就用 reactive 来代理 const convert = val => (isObject(val) ? reactive(val) : val); function toRaw(observed) { return (observed && toRaw(observed['__v_raw' /* RAW */])) || observed; } // ref 实现类 class RefImpl { constructor(_rawValue, _shallow = false) { this._rawValue = _rawValue; this._shallow = _shallow; this.__v_isRef = true; this._value = _shallow ? _rawValue : convert(_rawValue); } get value() { // track(toRaw(this), 'get' /* GET */, 'value'); return this._value; } set value(newVal) { if (hasChanged(toRaw(newVal), this._rawValue)) { this._rawValue = newVal; this._value = this._shallow ? newVal : convert(newVal); // trigger(toRaw(this), 'set' /* SET */, 'value', newVal); } } } // 创建一个 ref function createRef(rawValue, shallow = false) { return new RefImpl(rawValue, shallow); } // 暴露出去的方法,ref function ref(value) { return createRef(value); } // 暴露出去的方法,shallowRef function shallowRef(value) { return createRef(value, true); }
vue3响应式api
reactive:把对象变成响应式(深度监听)
shallowReactive:把对象变成响应式(浅层监听),只改变深层的属性,值会变,但是不会触发页面更新,如果伴随着第一层属性有变化,深层的值也会更新到页面上 readonly: 把响应式对象或纯对象变成 只读(深层),如果改变这个对象的值,就会警告,值不会发生变化;(最典型的是props传来的值是readonly,只能通过emit传到组件外部改变,保证了单项数据流) shallowReadonly:把响应式对象或纯对象变成 只读(浅层),第一层属性不能修改,但是深层的属性可以修改; ref:把基本类型和引用类型的值变成响应式,ref的初衷是将基本值转换成响应式,因为变成响应式需要是一个引用类型,但是比如number、string这中基本类型就没法直接使用proxy做响应式,于是ref是将基本类型转换成带有.value属性的引用类型,这样就可以转换成响应式了,当然,传入ref的值是引用类型时同样可以变成响应式,内部直接调用reactive变成响应式; isRef: 判断是不是ref对象 unref:展开ref对象,开发时不用再用.value去访问ref的值,template模板中统一将ref用unref展开了;function isRef(r) { return Boolean(r && r.__v_isRef === true); } function unref(ref) { return isRef(ref) ? ref.value : ref; }shallowRef: 浅层ref,也就是只对.value这一层做了响应式,再深层的就不是响应式了,所以当ref作用的值是基本类型时,用ref和shallowRef效果是一样的
triggerRef:个人理解是对shallowRef做的一个api,shallowRef只监听对value的改变,如果对深层的属性改变是不会触发页面渲染的,当改变深层属性后,用triggerRef再去触发页面更新
const shallowRefObj = shallowRef({ name: 'front-refined' }); // 这里不会触发副作用,因为是这个 ref 是浅层的 shallowRefObj.value.name = 'hello~'; // 手动执行与 shallowRef 关联的任何副作用,这样子就能触发了。 triggerRef(shallowRefObj);
customRef: 自定义的 ref 。这个 API 就更显式的让我们了解 track 与 trigger,看个例子:
name:{{name}}// ... setup() { let value = 'front-refined'; // 参数是一个工厂函数 const name = customRef((track, trigger) => { return { get() { // 收集依赖它的 effect track(); return value; }, set(newValue) { value = newValue; // 触发更新依赖它的所有 effect trigger(); } }; }); return { name }; }
toRef:可以用来为响应式对象上的 property 新创建一个 ref ,从而保持对其源 property 的响应式连接
const object = reactive({ name: 'tom', info: { age: 19 } }) const name = toRef(object, 'name');
name.value = 1; // object.name也会变成1
toRefs:把响应式对象的所有propety都创建成ref对象(内部是toRef实现)
compouted:计算属性(依赖响应式数据),如果计算公式里没有响应式数据,那么compouted就不是响应式数据
watch:监听响应式数据
watch( [() => state.id, () => state.name], ([id, name], [oldId, oldName]) => { /* ... */ } );watchEffect:和watch作用一样都是监听,但是watchEffect不用显示传入监听的值,会自动加载,第一个参数是一个函数,会立即执行,在这个函数里的变量都会被监听,这个函数有个 onInvalidate参数,也是个函数,会再再次出发effect时触发或者在异步函数结束时触发,可以在这里处理异步函数导致的因为异步导致的数值混乱问题; 具体的参考https://www.jianshu.com/p/a8fdf52d0bcf let a = ref(1)
watchEffect( (onInvalidate) => { console.log(a.value, 'a==='); onInvalidate(()=>{ console.log('清除'); }) }, { onTrigger(e) { console.log(e, 'onTrigger=='); }, onTrack(e) { console.log(e, 'onTrack==='); } }) setTimeout(() => { a.value += 1; }, 1000)isReadonly:判断是否是只读属性
isReactive:检查对象是不是reactive创建的响应式proxy
isProxy:检查对象是否是由 reactive 或 readonly 创建的 proxy。
toRaw: 把响应式对象转化为非响应式对象(去除响应式),当转化ref生成的响应式对象时,要传入refObject.value,才能正确转化,否则还会返回ref的格式
markRaw: 标记一个对象,使其永远不会转换为 proxy。返回对象本身。isRef:判断是否是 ref 对象