前言
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的具体过程:
- 响应式数据用proxy代理时get收集依赖
- 响应式数据用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()
}
});
- 被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方法再次代理
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)
}