zl程序教程

您现在的位置是:首页 >  前端

当前栏目

vue3响应式reactive的实现原理;proxy深层代理;vue3响应式api

Vue3响应代理原理API 实现 Proxy reactive
2023-09-11 14:19:39 时间

-

参考链接: 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,看个例子:

<template>
  <div>name:{{name}}</div>
  <input v-model="name" />
</template>

// ...
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时触发或者在异步函数结束时触发,可以在这里处理异步函数导致的因为异步导致的数值混乱问题;

  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 对象

-