vue3中reactive和ref的实现与区别详解

 更新时间:2023年10月23日 15:49:12   作者:BIBI居  
reactive和ref都是vue3实现响应式系统的api,他们是如何实现响应式的呢,reactive和ref又有什么区别呢,下面小编就来和大家详细讲讲,希望对大家有所帮助

前言

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

effect

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

<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方法,保存回调函数当数据更新再次调用回调函数达到更新视图

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的具体过程:

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

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

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执行依赖

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()
    }
  });
  • 被reactive包裹的数据实现响应式,首先得effec保存回调函数,方便数据更新再次调用来更新视图,
  • 当用proxy对对象代理时get方法中将视图用到的代码片段即相关effect与key一一对应保存起来
  • 当数据改变用到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方法再次代理

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)
}

到此这篇关于vue3中reactive和ref的实现与区别详解的文章就介绍到这了,更多相关 vue3 reactive ref内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Vuex和前端缓存的整合策略详解

    Vuex和前端缓存的整合策略详解

    这篇文章主要给大家介绍了Vuex和前端缓存的整合策略的相关资料,文中介绍的非常详细,对大家具有一定的参考学习价值,需要的朋友们下面随着小编一起来看看吧。
    2017-05-05
  • html-webpack-plugin修改页面的title的方法

    html-webpack-plugin修改页面的title的方法

    这篇文章主要介绍了html-webpack-plugin修改页面的title的方法 ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2020-06-06
  • 解决vue-loader加载不上的问题

    解决vue-loader加载不上的问题

    这篇文章主要介绍了解决vue-loader加载不上的问题,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧
    2020-10-10
  • Vue路由this.route.push跳转页面不刷新的解决方案

    Vue路由this.route.push跳转页面不刷新的解决方案

    这篇文章主要介绍了Vue路由this.route.push跳转页面不刷新的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-07-07
  • Vue路由组件通过props配置传参的实现

    Vue路由组件通过props配置传参的实现

    本文主要介绍了Vue路由组件通过props配置传参的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • Nuxt3项目搭建过程(Nuxt3+element-plus+scss详细步骤)

    Nuxt3项目搭建过程(Nuxt3+element-plus+scss详细步骤)

    这篇文章主要介绍了Nuxt3项目搭建(Nuxt3+element-plus+scss详细步骤),本次记录一次使用Nuxt3搭建前端项目的过程,内容包含Nuxt3的安装,基于Vite脚手架(默认)构建的vue3项目,element-plus的安装配置,scss的安装,目录结构的创建和解释,需要的朋友可以参考下
    2022-12-12
  • Vue3+Vue Router实现动态路由导航的示例代码

    Vue3+Vue Router实现动态路由导航的示例代码

    随着单页面应用程序(SPA)的日益流行,前端开发逐渐向复杂且交互性强的方向发展,在这个过程中,Vue.js及其生态圈的工具(如Vue Router)为我们提供了强大的支持,本文将介绍如何在Vue 3中使用Vue Router实现动态路由导航,需要的朋友可以参考下
    2024-08-08
  • vue监听对象及对象属性问题

    vue监听对象及对象属性问题

    这篇文章主要介绍了vue监听对象及对象属性问题,非常不错,具有一定的参考借鉴价值,需要的朋友可以参考下
    2018-08-08
  • vue项目API接口get请求传递参数方式

    vue项目API接口get请求传递参数方式

    这篇文章主要介绍了vue项目API接口get请求传递参数方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-07-07
  • 浅谈vuex之mutation和action的基本使用

    浅谈vuex之mutation和action的基本使用

    本篇文章主要介绍了浅谈vuex之mutation和action的基本使用,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2017-08-08

最新评论