vue3源码--实现reactive和ref

前言

reactive和ref都是vue3实现响应式系统的api,他们是如何实现响应式的呢?reactive和ref又有什么区别呢,看完这篇文章,相信答案就呼之欲出了。

effect

vue3中的reactive实现了数据的响应式,说到数据响应式离不开effect,effect是关键,effect()方法是暴露给创作者的一种方法,参数1是一个回调函数,写在回调函数的代码可以用来模拟setup执行函数的响应式数据被写在template模板的数据,当数据更新之后,将effect中的回调函数再次调用,达到数据更新便更新视图的目的。也可以把effect叫做数据相关依赖,即记录用到响应式数据的代码。参数二是一个对象,里面有lazy属性用来规定effect的回调函数第一次是否执行。

js 复制代码
<body>
    <div id="app">hello</div>
    <script>
        let data={
            name:'草原一匹狼'
        }
        effect(()=>{
            let app = document.getElementById('app')
            app.innerHTML=data.name
        },{lazy:true})
          setTimeout(()=>{
           data.name='切'
        },1000)
    </script>
</body>

实现effect方法,保存回调函数当数据更新再次调用回调函数达到更新视图

js 复制代码
export function effect(fn, options: any = {}) {//更新视图也就是吧视图用到数据在执行一遍
  const effect = createReactEffect(fn, options);
  if (!options.lazy) {//如果lazy:false执行
    effect();
  }
  return effect;
}
let uid = 0;
//每次调用都会创建一个effect
let activeeffect;//设置当前effect为全局变量好进行收集 
let effectStack = [];//保存effect 因为可能会有多个effect
function createReactEffect(fn, options) {
  const effect = function reactiveEffect() {
    if (!effectStack.includes(effect)) {
      try {
        effectStack.push(effect);
        activeeffect = effect;
        fn();
      } finally {
        effectStack.pop();
        activeeffect = effectStack[effectStack.length - 1];
      }
    }
  };
  effect.id = uid++; //区别effect
  effect._isEffect = true; //区别effect 是不是响应式的effect
  effect.raw = fn; //保存回调函数到自身
  effect.options = options; //保存用户属性lazy

  return effect;
}

reactive

了解完effect,接下来是实现reactive的具体过程:

  1. 响应式数据用proxy代理时get收集依赖
  2. 响应式数据用proxy代理时set执行依赖

1.响应式数据用proxy代理时get收集依赖

js 复制代码
function createGetter(isReadonly = false, shall = false) {
  return function get(target, key, receiver) {
    const res = Reflect.get(target, key, receiver); //taget[key]

    if (!isReadonly) {
      //判断是不是只读 收集依赖
      Track(target, TackOpType.GET, key);//Track收集依赖 一个响应式属性key对应一个effect保存起来
    }

    if (shall) {
      //只代理一层
      return res;
    }
    if (isObject(res)) {
      return isReadonly ? readonly(res) : reactive(res);
    }
    return res;
  };
}
//收集effect 获取数据触发get 收集依赖数据变化更新视图 key和effect11对应
let targetMap = new WeakMap(); //栈结构(target ,new Map(key(key effect一一对应),new Set(activeeffect)))
export function Track(target, type, key) {
  //对应的key
  if (activeeffect == undefined) {
    return;
  }
  let depMAp = targetMap.get(target);
  if (!depMAp) {
    targetMap.set(target, (depMAp = new Map()));
  }
  let dep = depMAp.get(key);
  if (!dep) {
    depMAp.set(key, (dep = new Set()));
  }
  if (!dep.has(activeeffect)) {
    dep.add(activeeffect);
  }
}

2.响应式数据用proxy代理时set执行依赖

js 复制代码
function createSetter(shall = false) {
  return function set(target, key, value, receive) {
    const oldValue = target[key];

    let hasKey =
      Array.isArray(oldValue) && isIntegerKey(key)
        ? Number(key) < target.length
        : hasOwn(target, key);
    const result = Reflect.set(target, key, value, receive); //获取最新的值 对象数据已经更新
    if (!hasKey) {
      // 新增
      trigger(target, TriggerOpTypes.ADD, key, value);
    } else {
      // 修改数组
      if (hasChange(value, oldValue)) {
        trigger(target, TriggerOpTypes.SET, key, value, oldValue);
      }
    }

    return result;
  };
}
//触发更新依赖effect
export function trigger(target, type, key?, newValue?, oldValue?) {
  const depsMap = targetMap.get(target);
  let effectSet = new Set();
  if (!depsMap) return;
  const add = (effectAdd) => {
    if (effectAdd) {
      effectAdd.forEach((effect) => effectSet.add(effect));
    }
  };
  add(depsMap.get(key)); //获取当前属性的effect
  //处理数组
  if (key == "length" && Array.isArray(target)) {
    depsMap.forEach((dep, key) => {
      if (key === "length" || key >= newValue) {
        add(dep);
      }
    });
  }else{
    //处理对象
    if(key !== undefined){
      add(depsMap.get(key))
    }
    switch(type){
      case TriggerOpTypes.ADD: 
      if(Array.isArray(target)&& isIntegerKey(key)){
        add(depsMap.get('target'))
      }
    }
  }
  effectSet.forEach((effect: any) => {
    if(effect.options.sch){
      effect.options.sch(effect)
    }else{
      effect()
    }
  });
  1. 被reactive包裹的数据实现响应式,首先得effec保存回调函数,方便数据更新再次调用来更新视图,
  2. 当用proxy对对象代理时get方法中将视图用到的代码片段即相关effect与key一一对应保存起来
  3. 当数据改变用到proxy的set方法时,用key找到对应effect执行

ref

相信大家都知道用reactive实现复杂数据代理用ref实现简单数据的代理,因为vue用es6的proxy代理整个对象,而ref是给简单数据用一个对象包裹起来用object.defineproperty方式代理并且用.value访问,如果用ref对一个对象进行响应式处理则会调用crtoReactive方法再次对.value的属性进行代理。 根据上面已经实现的reactive api实现ref就简单多了。具体步骤为: 1.如果是简单数据就用object.defineproperty代理 2.如果是复杂数据就用createReactive方法再次代理

js 复制代码
export function ref(target){
    return creatRef(target)
}

//创建实例对象
class RefImpl{
    public _v_isRef=true
    public _value
    
    public _shallow
    public _rawValue
    constructor(public target,public shallow){
        this._shallow=shallow
        this._rawValue = shallow ? target : toRaw(target)
        this._value = shallow ? target : toReactive(target)
    }
    get value(){
        Track(this,TackOpType.GET,"value")
        return this.value
    }
    set value(newVAlue){
        if(newVAlue!==this._value)
         this._rawValue=newVAlue 
         this._value = isObject(newVAlue) ? newVAlue : toReactive(newVAlue)//这是ref不失去响应式的关键如果新的值就重新代理
        trigger(this,TriggerOpTypes.SET,"value",newVAlue)


    }
}
function creatRef(target,shallow=false){
    //创建ref实例对象
    return new RefImpl(target,shallow)
}
相关推荐
M_emory_18 分钟前
解决 git clone 出现:Failed to connect to 127.0.0.1 port 1080: Connection refused 错误
前端·vue.js·git
Ciito21 分钟前
vue项目使用eslint+prettier管理项目格式化
前端·javascript·vue.js
成都被卷死的程序员1 小时前
响应式网页设计--html
前端·html
mon_star°1 小时前
将答题成绩排行榜数据通过前端生成excel的方式实现导出下载功能
前端·excel
Zrf21913184551 小时前
前端笔试中oj算法题的解法模版
前端·readline·oj算法
文军的烹饪实验室2 小时前
ValueError: Circular reference detected
开发语言·前端·javascript
Martin -Tang3 小时前
vite和webpack的区别
前端·webpack·node.js·vite
迷途小码农零零发3 小时前
解锁微前端的优秀库
前端
王解4 小时前
webpack loader全解析,从入门到精通(10)
前端·webpack·node.js
我不当帕鲁谁当帕鲁4 小时前
arcgis for js实现FeatureLayer图层弹窗展示所有field字段
前端·javascript·arcgis