likes
comments
collection
share

Vue3响应式及CompositionAPI实现

作者站长头像
站长
· 阅读数 41

依赖收集effect.ts(副作用函数)

  • 将模板转换的render函数放置在effect函数内,则实现了数据响应式。

流程简述

  1. 通过reactive创建响应式数据
  2. 在模板中使用该数据(访问触发Proxy.get),并将该模板转化为render渲染函数置于effect副作用函数中。
       // template => render => effect(render)
       // effect函数*默认*会先执行一次(绑定依赖)。
    
  3. 依赖收集:将正在执行的effect函数抛出为全局变量,并在渲染时通过响应式数据的get方法进行依赖收集绑定
  4. effect与响应式对象的属性互成依赖,两者为多对多关系
    • 一个effect可收集多个属性为依赖。存储对象deps
    • 一个属性可存在于多个effect中。存储对象weakMap:(对象:map(属性:set(effect)))
  5. 最后,当响应式数据改变时,根据依赖找到对应的effect函数=>effect(render())执行即可。
  6. 全流程均处于同步状态,精心设计利用了单线程进行依赖绑定。

核心:副作用函数effect

  • effect -- 副作用函数,如果此函数依赖的数据发生变化,则重新执行该函数(类似computed)
export function effect(fn, options: any = {}) {
  /*
    创建响应式的effect,effect可以根据状态变化,重新执行
    且可以嵌套着写,且可以传入自定义的调度函数[scheduler],可实现异步更新
  */
  const _effect = new ReactiveEffect(fn, options.scheduler);
  _effect.run(); // 默认先执行一次

  // effect执行完可以重复执行其中的方法 runner._effect.run/stop等,绑定this指向
  const runner = _effect.run.bind(_effect);
  runner._effect = _effect;
  return runner;
}

响应式类 - ReactiveEffect

  • 将effect转为响应式(嵌套监听逻辑)
// activeEffect 当前激活的effect函数
// 根据单线程原理可以追踪到每一个effect
export let activeEffect = undefined;

export class ReactiveEffect {
  /**
   * 为实例新增属性
   * public  => this.active = true 默认为激活状态
   *  => this.parent = null 为实例新增父亲[树形结构],避免嵌套函数回退时,全局属性丢失
   *  => this.deps = []  [分支切换]effect记录keys,记录被谁收集过,当重新执行effect时,要清空上次收集的依赖
   *  => this.scheduler = getters 当用户需要自定义调度时,可以使用scheduler
   */
  public active = true;
  public parent = undefined;
  public deps = []; //
  constructor(public fn, public scheduler?) {}
  run() {
    // 若函数不需要响应式数据,则说明未激活,直接执行函数即可
    if (!this.active) {
      this.fn();
    }
    try {
      // 此时activeEffect为null或者其父亲(当effect嵌套时,要精准绑定响应式数据与effect的关系)
        this.parent = activeEffect;
      // 依赖收集,先改变全局activeEffect值,让渲染函数与依赖关联
      activeEffect = this;
      // 执行effect函数前,要清空上次的依赖
      clearupEffect(this);
      return this.fn();
    } finally {
      activeEffect = this.parent;
    }
  }
  stop() {
    if (this.active) {
      this.active = false;
      clearupEffect(this);
    }
  }
}

// 执行effect函数前,要清空上次的依赖
function clearupEffect(effect) {
  const { deps } = effect;
  for (let i = 0; i < deps.length; i++) {
    // 此时操作的 deps[i] === targetMap.get(target).get(key)
    deps[i].delete(effect); // 调用对应的key删除对应的effect -- [Set]对象引用
  }
  deps.length = 0; // 删除effect中的deps记录
}

依赖绑定函数track

/**
 * 对象[属性]收集函数[多对多]
 *
 * 树形结构targetMap -- 源Map
 *  第一层:以对象为键,Map为值 targetMap.set(target, (depsMap = new Map()))
 *           一个对象[targetMap] -- 多个属性[target]
 *  第二层:以属性为键,Set为值[保证其唯一性],
 *           一个对象[key] -- 多个属性[Set]
 *
 */
// 弱引用 -- WeakMap键只能是引用类型的对象
// 不会导致内存泄漏,原对象删除时,该引用也删除
const targetMap = new WeakMap(); 
export function track(target, type, key) {
  if (!activeEffect) return; // 全局环境下访问属性值,不做处理
  let depsMap = targetMap.get(target); // 第一次没有
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map())); // 注入target标识
  }
  let dep = depsMap.get(key); // dep => 属性key对应的唯一值Set对象
  if (!dep) {
    depsMap.set(key, (dep = new Set()));
  }
  // 给当前的dep -- Set对象,绑定effect
  trackEffects(dep);
}

依赖更新执行effect函数trigger

/**
 *
 * trigger 通知effect执行函数
 */

export function trigger(taregt, type, key, value, oldVal) {
  // 获取对象值
  const depsMap = targetMap.get(taregt);
  // 触发的值,不在模板中
  if (!depsMap) return;
  let effects = depsMap.get(key); // 找到属性收集到的effects
  // 通知收集的依赖执行

  if (effects) {
    // 拷贝一份,防止清理与添加的操作同时发生,关联引用
    triggerEffect(effects);
  }
}

封装effect常用逻辑收集、调用


// 封装收集依赖
export function trackEffects(dep) {
  if (activeEffect) {
    let shouldTrack = !dep.has(activeEffect); // 性能优化,由用户判断
    if (shouldTrack) {
      // 为dep对象绑定effect
      dep.add(activeEffect);
      // 反向记录,当effect绑定值被切换时[flag ? state.name : state.age],通知key值,别更新该effect
      activeEffect.deps.push(dep);
    }
  }
}

// 封装触发effect函数
export function triggerEffect(effects) {
  effects = new Set(effects);
  effects.forEach((effect) => {
    /**
     *  防止effect中再次修改同一属性值[循环引用]
     *  run的时候会删除上一个effects,然后再添加本次执行的deps。
     *  深拷贝一份,解除关联引用。循环数组时,delete和add同时运行,会造成栈溢出。
     */
    if (effect !== activeEffect) {
      if (effect.scheduler) {
        effect.scheduler();
      } else {
        effect.run();
      }
    }
  });
}

数据代理(reactive实现)

代理流程简述

  • reactive:将对象代理,并返回代理对象。
  • 考虑对象中有访问器、函数,在其中通过this访问对象的属性。
    • 劫持时通过Reflect获取、修改属性,并将Proxy对象传入,改变其this指向为Proxy对象。
    • 保证访问器、函数内的源对象属性访问也能被劫持到。
  • 处理已代理过的对象,通过标识符IS_REACTIVE,标识数据是否已代理过
  • 若已代理过,则直接返回该对象。
  • 细节:
    • 未被代理的对象无该属性拦截。
    • 已被代理的对象在访问器设置了逻辑。
    • 在不新增属性的情况下实现了标识判断。
// 导入值本来应该作为公共导出的,为了方便,下面直接贴上了

// import { isObject } from "@vue/shared"; 
// import { mutableHandle, ReactiveFlags } from "./baseHandle";

// 判断对象是否为响应式对象
export const isReactive = (obj) => {
  return obj && obj[ReactiveFlags.IS_REACTIVE];
};

// 响应式标识
export const enum ReactiveFlags {
  IS_REACTIVE = "__v_isReactive",
}

export const isObject = (example) => {
  return typeof example === "object" && example != null;
};

// 数据拦截核心逻辑 -- new Proxy(mutableHandle)
export const mutableHandle = {
  // 拦截器逻辑
  get(target, key, receiver) {
    /**
      activeEffect ,当响应式数据被访问时,可以监听到,并关联到该渲染函数
      console.log("key", key); 
      为代理对象新增已代理标识
     */
    
    if (key === ReactiveFlags.IS_REACTIVE) {
      return true;
    }
    // 收集依赖函数
    track(target, "get", key);

    /**
     * 懒递归,当访问代理对象A中的值B依旧为对象时,将B代理并返回,且不会重复代理
     * receiver => mutableHandle -- 代理对象
     * 监控代理对象获取值,Reflect 可以改变对象中的访问器this指向,指向代理对象receiver
     *
     */
    let res = Reflect.get(target, key, receiver);
    if (isObject(res)) {
      return reactive(res);
    }
    return res;
  },
  set(target, key, value, receiver) {
    // 通知更新
    let oldVal = target[key];
    let result = Reflect.set(target, key, value, receiver);
    if (oldVal != result) {
      trigger(target, "set", key, value, oldVal);
    }

    // target指向源对象,receiver指向代理对象
    return Reflect.set(target, key, value, receiver);
  },
}

// reactive方法逻辑
export function reactive(obj) {
  if (!isObject(obj)) return;
  if (obj[ReactiveFlags.IS_REACTIVE]) {
    return obj;
  }

  const proxyObj = new Proxy(obj, mutableHandle);
  return proxyObj;
}



源码阅读补充

  • 部分属性不代理如:Symbol(内置)、__proto__等(对象内置属性)、__v_isRef、__isVue_等
  • 如果对象存在原型链继承的关系原型链上存在此属性,后子级直接修改该属性,则只触发当前对象利用toRaw获取源对象判断是否为当前对象
  • 数组处理循环递归
    • 重写数组部分方法,当数组方法需要用到源数组做操作或对比时includes、indexOf,内部使用源对象进行操作,保证结果准确。
    • 当数组通过修改长度值为newLen
      • 比对newLen值与数组下标项,若小于则触发更新
      • 直接监听数组长度也会触发更新
    • 重写通过数组方法修改数组本身的方法,(push,pop,shift,unshift,splice)
      • 当执行时,会收集数组的length属性,并触发length属性的依赖更新,及自身的依赖更新等
  • Reflect补充图例:Vue3响应式及CompositionAPI实现

ref实现

  • API们
    • ref:将一个基本数据类型转换为响应式数据类型。
      • 将原始值包装成对象defineProperty,与computed的原理相似
        • 若是复杂数据类型,则走reactive,转为响应式数据
      • 并设置get访问器收集依赖、set触发依赖
    • toRefs:将对象所有属性再代理一层,访问时代理到源对象上
    • toRef:代理对象一个属性
    • proxyRefs:反向代理,将响应式数据又反向代理成普通数据使用,本质还是访问.value
      • 用于模板中访问数据。
import { isArray, isObject } from "@vue/shared";
import { trackEffects, triggerEffect } from "./effect";
import { reactive } from "./reactive";

function toReavtive(val) {
  return isObject(val) ? reactive(val) : val;
}

/**
 * 判断传入的值,若为引用类型,则将其响应式化,最后将其defineProperty化[拦截]
 *  收集依赖
 *  值发生改变:触发更新
 *      判断传入的值是否为引用类型
 *
 *
 */
class RefImpl {
  public dep = new Set();
  public _value;
  public __v_isRef = true;
  constructor(public source) {
    this.source = toReavtive(source);
  }
  get value() {
    trackEffects(this.dep);
    return this._value;
  }
  set value(newVal) {
    if (newVal !== this.source) {
      this._value = toReavtive(newVal);
      this.source = newVal;
      triggerEffect(this.dep);
    }
  }
}

/**
 *
 * @param source 原始值
 *
 */
export const ref = (source) => {
  return new RefImpl(source);
};

// 将数据做一层代理[将解构后的响应式数据再用访问器的方式包裹一次,并与源数据保持引用]。正向代理
class ObjectRefImpl {
  constructor(public obj, public key) {}
  get value() {
    return this.obj[this.key];
  }
  set value(newVal) {
    this.obj[this.key] = newVal;
  }
}

// 将单个响应式数据包裹
export const toRef = (obj, key) => {
  return new ObjectRefImpl(obj, key);
};

// 用于将多个响应式数据解构[将解构后的响应式数据再用访问器的方式包裹一次,并与源数据保持引用]
export const toRefs = (obj) => {
  // 判断obj是对象还是数组
  const result = isArray(obj) ? new Array(obj.length) : obj;
  for (let key in result) {
    toRef(result, key);
  }
};

// 将ref数据反向代理,用于模板中访问ref数据不用.value

export const proxyRef = (obj) => {
  return new Proxy(obj, {
    get(target, key, recevier) {
      let r = Reflect.get(target, key, recevier);
      // 判断该数据是否是ref数据,如果不是[没有.value],直接返回
      return r.__v_isRef ? r.value : r;
    },
    set(target, key, value, recevier) {
      let oldVal = target[key];
      if (oldVal.__v_isRef) {
        // 获取旧值,如果旧值是ref数据,则赋值给其value值
        oldVal.value = value;
        return true;
      } else {
        // 如果不是,则直接赋值
        return Reflect.set(target, key, value, recevier);
      }
    },
  });
};


Watch原理

简介

  • 使用方式:传入一个依赖:(对象/方法),一个回调方法,当依赖值改变时,执行回调。

  • 例子

const obj = reactive({
  name:'zs',
  age:12
})
// 监听函数返回值
watch(()=>obj.name,(newVal,oldVal)=>{
  console.log('obj.name change')
})

// 监听对象并自定义处理逻辑
watch(obj,(newVal,oldVal,handle)=>{
  console.log(`obj's key  change`)
  if(newVal['name'] === oldVal['name']){
    handle(()=>{
      console.log(`obj's name has not change`)
    })
  }
})

原理简述

  • 将传入的依赖都整合成函数,effect化,当依赖改变时,执行回调函数。

  • 整合参数类型:都处理成函数

    • 对象 -- 将该对象放置在一个函数内,并在函数内递归访问所有属性。
    • 函数 -- 直接将该函数放置在effect回调内。
  • 响应式处理

    • 根据传入的函数执行后的返回值,当函数依赖属性改变时,重新获取一次该值并传入回调中。
  • 自定义调度器回调的第三个参数

    • watch提供一种自定义逻辑回调,当触发watch更新时可执行当次未更新前传入的逻辑
    • 特殊场景下可使用:如输入框多次触发异步请求,无法判断先后,则可以在更新时抵消掉上次的逻辑。
import { isFunction, isObject } from "@vue/shared";
import { ReactiveEffect } from "./effect";
import { isReactive } from "./reactive";
/**
 *
 * @param source 用户传入的数据 -- 函数/响应式对象,对象无法区分新旧值。
 * @param cb 回调,内有新旧值
 */
export const watch = (source, cb, options) => {
  let getter;
  // 判断传入的值是否为响应式对象
  if (isReactive(source)) {
    /*
      watch本质是要转为effect,ReactiveEffect接收一个函数
      传入的值为响应式对象
      将getter effect化,将监听对象source放入函数内.
      并在函数内递归[traversal]访问每一个属性
    */
    getter = () => traversal(source);
  } else if (isFunction(source)) {
    // 传入的值为函数
    console.log("isFunction");
    // 如果是函数,则直接用其传入值作为响应式函数即可
    getter = source;
  } else {
    return;
  }

  let oldVal;

  let cleanup;
  let onCleanup = (fn) => {
    cleanup = fn;
  };

  let watchScheduler = () => {
    /*
     用户自定义的执行逻辑,第一次不执行,
        watch更新时执行上一次调度传入的函数[cleanup]。
     如:input框监听值并发送ajax请求,
        可能出现后面的请求比前面的请求先返回,导致返回值混乱的问题
     可以定义对应的锁[shock],进行顺序界定
     watch(()=>state.age,(newVal,oldVal,cleanup)=>{
      cleanup(()=>{
        传入一个fn,将fn赋值给cleanup
        在下一次effect执行时执行。
      })
     })
    */
    if (cleanup) cleanup();
    // watch调度器,当依赖变化时,调度回调执行,并传入新旧值
    const newVal = effect.run();
    cb(newVal, oldVal, onCleanup);
    // 当回调执行完时,老值变新值,待下次执行
    oldVal = newVal;
  };

  // 监控getter,当其依赖变化时
  const effect = new ReactiveEffect(getter, watchScheduler);
  oldVal = effect.run(); //获取getter第一次执行返回的对象
};

// 递归访问对象属性
function traversal(obj, set = new Set()) {
  // 出口,非对象则不递归了
  if (!isObject(obj)) {
    return obj;
  }
  // 避免循环引用的问题,即属性访问自身或者自身上的引用对象
  // 无限递归 -- 爆栈
  if (set.has(obj)) {
    return obj;
  }
  set.add(obj);
  for (let key in obj) {
    traversal(obj[key], set);
  }
  return obj;
}

Computed原理

简介

  • 作用:得到一个新的响应式数据(缓存),并通过.value可以获取到回调返回值。

  • 本质是基于effect函数的,缓存功能借由dirty判断,且本身需要具备作为依赖收集上级函数的能力

  • 例子

// 处理传过来的getter和setter
const com = computed(()=>{
    return obj.a + obj.b
})
console.log(com.value); value => proxy
computed({
  get(){
    return obj.a + obj.b
  },
  set(){...}
})

实现简述

  1. 整合传入参数getters、setter,通过computedRefImpl类处理即可
  2. computedRefImpl类内部实现。
    • constructor
      1. 收集内部依赖,将getter传入的函数effect化,自定义依赖集合deps分发。
      2. 自定义调度器,由脏值dirty决定是否触发更新。
    • value属性访问器:
      • get:监听value属性,收集收集者们入deps:当value的值变化时,通知该收集者们刷新视图。
      • set:执行传入的回调(若有)。
import { isFunction } from "@vue/shared";
import { ReactiveEffect, trackEffects, triggerEffect } from "./effect";

/*
    ReactiveEffect => effect主类,用于创建effect函数
    trackEffects => effect收集依赖
    triggerEffect => effect触发更新,执行回调
*/

export const computed = (getterOrOptions) => {
  let onlyGetter = isFunction(getterOrOptions),
    setter,
    getter;

  // 固定参数,不管怎么传,逻辑一致
  if (onlyGetter) {
    // 如果effectOptions是函数,说明是回调用法,反之是访问器用法
    getter = getterOrOptions;
    setter = () => {
      console.warn("can not set");
    };
  } else {
    setter = getterOrOptions.set;
    getter = getterOrOptions.get;
  }
  return new computedRefImpl(getter, setter);
};

/**
 * 交由computedRefImpl处理
 * 处理规则
 *    将getter用effect处理一下,收集里面的依赖
 *    再配合scheduler,当数据被访问时,判断dirty,进行调度
 */
class computedRefImpl {
  public effect;
  public _dirty = true; // 标识是否需要计算[脏值],默认取值的时候就要计算
  public _v_isReadonly = true;
  public _v_isRef = true;
  public _value;
  // 引用对象,用于收集当前computed.value的effect,当effect执行的时候全局activeEffect会修改
  public dep = new Set(); 
  constructor(getter, public setter) {
    /**
      computed(()=>state.name + '先生')
      state.name 修改 => getter 重新执行 
        => getter返回值修改(getter.value) 
        => 外部effect重新执行
     
      核心逻辑
        getter => computed(getter) / computed({get:getter})
        1. [getter收集底层依赖属性] -- new ReactiveEffect(getter, scheduler)
        2. [computed.value作为响应式属性需要收集effect] -- computedRefImpl.get
       底层依赖属性修改
        1.computed重新执行并获取返回值给dirty;
        2.computed.value收集的effect要触发更新[computed.value <=> effect == 一对一]
     */
    this.effect = new ReactiveEffect(getter, 
    // 此处是一个scheduler,当getter收集的响应式属性改变,scheduler触发
    () => {
      console.log("scheduler");
      if (!this._dirty) {
        this._dirty = true;
        // 触发computed.value收集的effect更新,修改视图
        triggerEffect(this.dep);
      }
    });
  }
  get value() {
    /**
     * 需要做依赖收集,当computed.value被effect包裹的时候
       需要将该effect收集起来,待computed.value修改时,才能由scheduler触发
       类中的属性访问器,本质还是ES5[Object.defineProperty]实现
       访问computed.value => 执行该回调
     */
    trackEffects(this.dep);// 收集依赖属性
    if (this._dirty) {
      // 让传入的fn执行,并记录返回值
      this._dirty = false;
      this._value = this.effect.run();
    }
    return this._value;
  }
  set(value) {
    this.setter(value);
  }
}

总结

  1. template开始 => template => render(h) => effect(render(h))
  2. effect执行
    1. 将传入的fn(render),包装成响应式函数对象effect,拥有自己的依赖数组deps,用于收集响应式数据
      • 该deps结构为Set([key1,key2]),每个key即是属性收集的effect(对象引用)。
    2. 抛出该effect到全局变量activeEffect
  3. fn执行 => render(h)
    1. 读取到响应式代理API(ref、reactive、computed等)
    2. 通过proxy代理传入的数据obj,将其包装成响应式数据,拥有自己的依赖函数数组deps,用于收集响应式effect
      • 该deps结构为{obj:{key1:Set[effect1,effect2],key2:map2},key1中的数据与effect收集的响应式数据一致,对象引用
      • 该deps被全局收集targetMap(weakMap),用于判断该对象是否被收集过
    3. 通过Proxy整体拦截对象(懒递归)
      1. 访问属性时调用track、trackEffect收集effectkey.deps数组。
      2. 修改属性时调用trigger、triggerEffect触发effect.deps数组执行。
  4. 分支处理:执行effect函数前,需要将上一次属性收集的effect依赖删除(clearupEffect--对象引用)。

effect

副作用函数,如果此函数依赖的数据发生变化,则重新执行该函数 [computed/watch的原理函数]

  • 收集依赖[响应式数据中的key],依赖数据变化,重新渲染页面[响应式原理=>数据驱动视图]
  • 多对多
    • 一个属性 <=> 多个effect
    • 一个effect <=> 多个属性
    • 双向记录
      • 属性记录函数,为了通知函数改变
      • 函数记录属性,当函数删除时,需要通知属性切断依赖
  • 优化:过滤更新Map+Set
    • 若存在多个相同属性绑定同个依赖则后续绑定不生效Map+Set

reactive

将对象通过Proxy+Reflect代理成响应式数据。

  • 源码阅读
    • 部分属性不代理如:Symbol(内置)、__proto__等(对象内置属性)、__v_isRef、__isVue_等
    • 如果对象存在原型链继承的关系原型链上存在此属性,后子级直接修改该属性,则只触发当前对象利用toRaw获取源对象判断是否为当前对象
    • 数组处理循环递归
      • 重写数组部分方法,当数组方法需要用到源数组做操作或对比时includes、indexOf,内部使用源对象进行操作,保证结果准确。
      • 当数组通过修改长度值为newLen
        • 比对newLen值与数组下标项,若小于则触发更新
        • 直接监听数组长度也会触发更新
      • 重写通过数组方法修改数组本身的方法,(push,pop,shift,unshift,splice)
        • 当执行时,会收集数组的length属性,并触发length属性的依赖更新,及自身的依赖更新等

computed

  • 本质:computed内创建一个effect,且该effect又作为所处环境的effect依赖项被收集起来。
    • 所以当computed的依赖项修改时,不仅computed本身需要重新计算,还要通知其依赖函数渲染视图。
    • key修改时 => fn重新执行,返回新的val => 环境effect重新执行
  • 核心逻辑
    • 处理参数,统一gettersetter
    • 调度getter=>computedRefImpl
      • 收集依赖 -- computed收集底层依赖属性且computed.value需要作为属性需要收集effect
      • 底层依赖修改 -- 1.computed重新执行并修改脏值dirty; 2.computed.value收集的effect要触发更新computed.value <=> effect == 多对多
      • 通过调度器scheduler和脏值dirty触发computed函数
    • 需要做依赖收集,当computed.value被effect包裹的时候,需要将该effect收集起来,待computed.value修改时,才能由scheduler触发

watch

根据传入的配置,创建响应式effect,当依赖属性改变时,触发回调执行。

  • 本质还是effect,监听对象与该effect互为依赖,传入source,cb,options
  • 判断传入的数据source
    • 是函数,则直接收集该函数的返回值fnReturn为依赖(不递归,若fnReturn为引用对象,只收集fnReturn,不收集其下属性)
    • 是响应式对象,则将该对象用函数包裹起来,并在函数内收集该对象的每个属性递归:const fn => travals(obj)
  • 将该回调函数effect化,并自定义对应的调度器watchScheduler,当依赖改变时,调度器执行传入的回调cb,并将新旧值传入
    • 函数effect化时能获取到该响应式对象,保存起来,后期改变时传入newVal,oldVal
    • 改变后,新值赋值给旧值,下次改变时传入
    • 提供用户自定义逻辑cleanup,判断watch执行逻辑

ref

将一个基本数据类型转换为响应式数据类型,并拦截其value属性

  • 将原始值包装成对象defineProperty,与computed的原理相似
  • 若是复杂数据类型,则走reactive,转为响应式数据
  • 并设置get访问器收集依赖、set触发依赖

toRefs

解构reactive对象,将被解构的静态数据代理成对象,代理其value属性,访问时代理到源对象上。

toRef

代理对象一个属性

proxyRefs

反向代理,访问ref数据时,不用访问其value属性,内部返回其value属性。一般用于模板渲染ref属性。

其余API

基于核心实现的API

  • shallowReactive -- 浅转换只转化第一层
  • readonly -- 深度转换且数据只读
  • shallowReadonly -- 浅转换且只读
  • toRaw -- 获取代理对象源数据[重写数组方法获取源数组用到,如indexOf,includes等等]
    • 默认情况下,响应式数组获取值也是被代理过的,与原值不等[这会造成用户操作失误]。
  • markRaw -- 标记对象不可被代理且不可枚举
转载自:https://juejin.cn/post/7239617977363071035
评论
请登录