• 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,看个例子:

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

    -

  • 相关阅读:
    css3 边框、背景、文本效果
    Java JDBC连接MYSQL数据库教程
    waf平台常用方法总结
    java比较两个日期大小
    js控制的弹出层
    js时间大小判断写法demo
    PL/SQL Developer技巧
    杀Oracle死锁进程方法
    查看oracle数据库的连接数以及用户
    Oracle分散问题记录
  • 原文地址:https://www.cnblogs.com/fqh123/p/16340077.html
Copyright © 2020-2023  润新知