vue3中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内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
相关文章
html-webpack-plugin修改页面的title的方法
这篇文章主要介绍了html-webpack-plugin修改页面的title的方法 ,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧2020-06-06Vue路由this.route.push跳转页面不刷新的解决方案
这篇文章主要介绍了Vue路由this.route.push跳转页面不刷新的解决方案,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教2021-07-07Nuxt3项目搭建过程(Nuxt3+element-plus+scss详细步骤)
这篇文章主要介绍了Nuxt3项目搭建(Nuxt3+element-plus+scss详细步骤),本次记录一次使用Nuxt3搭建前端项目的过程,内容包含Nuxt3的安装,基于Vite脚手架(默认)构建的vue3项目,element-plus的安装配置,scss的安装,目录结构的创建和解释,需要的朋友可以参考下2022-12-12
最新评论