Vue reactive函数实现流程详解

目录
  • 1.Reflect
  • 2.Proxy的工作原理
  • 3.代理Object
  • 4.合理的触发响应
  • 5.深响应和浅响应
  • 6.只读和浅只读
  • 7.代理数组

1.Reflect

  Proxy有着可以拦截对对象各种操作的能力,比如最基本的get和set操作,而Reflect也有与这些操作同名的方法,像Reflect.set()、Reflect.get(),这些方法和它们所对应的对象基本操作完全一致。

const data = {
    value: '1',
    get fn() {
        console.log(this.value);
        return this.value;
    }
};
data.value; // 1
Reflect.get(data,'value'); // 1

  除此之外,Reflect除了和基本对象操作等价外,它还具有第三个参数receiver,即指定该基础操作的this对象。

Reflect.get(data,'value',{<!--{cke_protected}{C}%3C!%2D%2D%20%2D%2D%3E-->value: '2'}); // 会输出2

  对于Proxy,它只能够拦截对象的基本操作,而对于data.fn(),这是一个复合操作,它由一个get操作和一个apply操作组成,即先通过get获取fn的值,然后调用即apply对应的函数。而现在,用我们之前创建的响应式系统来执行一次这个复合操作,我们期望的结果是,在对fn属性绑定的同时,对value的值也进行绑定,因为在fn函数的执行过程中,操作了value值。可实际情况是,value的值并没有进行绑定。

effect(() => {
	obj.fn(); // 假设obj是一个已经做了响应式代理的Proxy对象
})
obj.value = '2'; // 改变obj.value的值,预想中的响应式操作没有执行

  这里就涉及到fn()函数中,this指向的问题了。实际上,在fn函数中,this指向的是原来的data对象,即this.value实际上是data.value,因为操作的是原对象,因此并不会触依赖收集。了解到问题的原因之后,我们就可以用上之前所说的Reflect的特性了,将get操作实际的this对象指定为obj,这样就可以顺利的实现我们我期望的功能了。

const obj = new Proxy(data, {
  get(target, key, receiver) { // get 接收第三个参数,即操作的调用者,对应obj.fn()就是obj了
    track(target, key);
    return Reflect.get(target, key, receiver); // 将原来直接返回target[key]的操作改为Reflect.get
  }
}

2.Proxy的工作原理

  在js中一个对象必须部署包括[[GET]]、[[SET]]在内的11个内部方法,除此之外,函数拥有额外的[[Call]]和[[Construct]]两个方法。而在创建Proxy对象时,指定的拦截函数,实际上就是用来自定义代理对象本身的内部方法和行为,而不是指定。

3.代理Object

(1)代理读取操作

对一个普通对象的所有可能的读取操作:

  • 访问属性:obj.foo
  • 判断对象或原型上是否存在给定的key;key in obkj
  • 使用for … in循环遍历对象

  首先对于基本的访问属性,我们可以使用get方法拦截。

const obj = new Proxy(data, {
  get(target, key, receiver) { // get 接收第三个参数,即操作的调用者,对应obj.fn()就是obj了
    track(target, key);
    return Reflect.get(target, key, receiver); // 将原来直接返回target[key]的操作改为Reflect.get
  }
}

  然后,对于in操作符,我们使用has方法进行拦截。

has(target, key) {
    track(target, key);
    return Reflect.has(target,key);
}

  最后,对于for … in操作,我们使用ownKeys方法进行拦截。这里使用和唯一标识ITERATE_KEY和副作用函数绑定,因为对于ownKeys操作来说,无论如何它都是对一个对象上所存在的所有属性进行遍历,并不会产生实际的属性读取操作,因此我们需要用一个唯一的标识来标记ownKeys操作。

ownKeys(target, key) {
  // 这里将副作用函数和唯一标识ITERATE_KEY绑定了
  track(target, ITERATE_KEY);
  return Reflect.ownKeys(target);
},

  相应的,在进行赋值操作的时候,也需要相应的对ITERATE_KEY这个标识进行处理

function trigger(target, key) {
  const depsMap = bucket.get(target);
  if (!depsMap) return;
  const effects = depsMap.get(key);
  const iterateEffects = depsMap.get(ITERATE_KEY); // 读取ITERATE_KEY
  const effectToRun = new Set();
  effects &&
    effects.forEach((fn) => {
      if (fn !== activeEffect) {
        effectToRun.add(fn);
      }
    });
  // 将与 ITERATE_KEY 相关联的副作用函数也添加到 effectsToRun
  iterateEffects &&
    iterateEffects.forEach((fn) => {
      if (fn !== activeEffect) {
        effectToRun.add(fn);
      }
    });
  effectToRun.forEach((fn) => {
    if (fn.options.scheduler) {
      fn.options.scheduler(fn);
    } else {
      fn();
    }
  });
}

  虽然以上的代码解决了添加属性的问题,但是随之而来的是修改属性的问题。对于for … in循环来说,无论原对象的属性如何修改,对它来说只需要进行一次遍历就好了,因此我们需要区分添加和修改的操作。这里使用Object.prototype.hasOwnProperty检查当前操作的属性是否已经存在于目标对象上,如果是,则说明当前的操作类型是’SET‘,否则说明是’ADD‘。然后将type作为第三个参数,传入trigger函数中。

set(target, key, newVal, receiver) {
    const type = Object.prototype.hasOwnProperty.call(target, key)
      ? "SET"
      : "ADD";
    Reflect.set(target, key, newVal, receiver);
    trigger(target, key, type);
},

(2)代理delete操作符

  代理delete操作符使用的是deleteProperty方法,因为delete操作符删除属性会导致属性的数量变少,因此当操作类型为DELETE时也要触发一下for … in循环的操作。

deleteProperty(target, key) {
    // 检查删除的key是否为自身属性
    const hadKey = Object.prototype.hasOwnProperty.call(target, key);
    const res = Reflect.deleteProperty(target, key);
    if (res && hadKey) {
      trigger(target, key, "DELETE");
    }
    return res;
},
// 当type为ADD或DELETE的时候,才执行ITERATE_KEY相关的操作
if (type === "ADD" || type === "DELETE") {
    iterateEffects &&
      iterateEffects.forEach((fn) => {
        if (fn !== activeEffect) {
          effectToRun.add(fn);
        }
      });
}

4.合理的触发响应

(1)完善响应操作

  触发修改操作时,若新值和旧值相等,则不需要触发修改响应操作。

set(target, key, newVal, receiver) {
    const oldVal = target[key];  // 获取旧值
    const type = Object.prototype.hasOwnProperty.call(target, key)
      ? "SET"
      : "ADD";
    const res = Reflect.set(target, key, newVal, receiver);
    if (oldVal !== newVal) { // 比较新值和旧值
      trigger(target, key, type);
    }
    return res;
},

  但是全等有一个特殊情况,就是NaN === NaN的值为false,因此我们需要对NaN进行一个特殊判断。

(2)封装一个reactive函数

  其实就是对new Proxy进行了一个简单的封装。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, newVal, receiver) {
      const oldVal = target[key]; // 获取旧值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
        // 比较新值和旧值
        trigger(target, key, type);
      }
      return res;
    },
    has(target, key) {
      track(target, key);
      return Reflect.has(target, key);
    },
    ownKeys(target, key) {
      // 这里将副作用函数和唯一标识ITERATE_KEY绑定了
      track(target, ITERATE_KEY);
      return Reflect.ownKeys(target);
    },
    deleteProperty(target, key) {
      // 检查删除的key是否为自身属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (res && hadKey) {
        trigger(target, key, "DELETE");
      }
      return res;
    },
  });
}

  现在,我们使用reactive创建两个响应式对象,child和parent,然后将child原型设置为parent。然后为child.bar函数绑定副作用函数。当修改child.bar的值的时候,可以看到,副作用函数实际执行了两次。这是因为,child的原型是parent,child本身并没有bar这个属性,所以根据原型链的规则,最终会在parent身上拿到bar这个属性。因为在进行原型链查找的过程中,访问到了parent上的属性,因袭进行了一次额外的绑定操作,所以最终副作用函数执行了两次。

const obj = {};
const proto = {
  bar: 1,
};
const child = reactive(obj);
const parent = reactive(proto);
Object.setPrototypeOf(child, parent);
effect(() => {
  console.log(child.bar);
});
child.bar = 2; // 输出 1 2 2

  这里我们比较一下child和parent的拦截函数,可以发现receiver的值都是相同的,发生变化的是target的值,因此我们可以通过比较taregt的值来取消parent触发的那一次响应操作。

// child 的拦截函数
get(target, key, receiver) {
// target是原始对象obj
// receiver 是child
}
// parent 的拦截函数
get(target, key, receiver) {
// target是proto对象
// receiver 是child
}

  这里我们通过添加一个raw操作来实现,当访问raw属性的时候,会返回该对象的target值。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 添加一个新值 raw
        return target;
      }
      track(target, key);
      return Reflect.get(target, key, receiver);
    },
    set(target, key, newVal, receiver) {
      const oldVal = target[key]; // 获取旧值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        // 比较target值,如果receiver的target和当前target相同,说明就不是原型链操作。
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 比较新值和旧值
          trigger(target, key, type);
        }
      }
      return res;
    }
  }
}

5.深响应和浅响应

  实际上,前面我们实现的reactive还只是浅层响应,也就是说只有对象的第一层具有响应式反应。比如对于一个obj:{bar{val:1}}对象,当对obj.bar.val进行操作的时候,我们首先从obj中拿到bar,但是这时候的bar只是一个普通对象bar:{val:1},因此无法进行响应式操作。这里我们对Reflect.get获取的值进行一个判断,如果拿到的值是一个对象,递归调用reactive函数,最后拿到一个深层响应的对象。

function reactive(obj) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        return target;
      }
      track(target, key);
      const res =  Reflect.get(target, key, receiver);
      if(typeof res === 'object') {
          return reactive(res);
      }
      return res;
    }
  }
}

  但是我们并非所有时候都期望深层响应,因此我们调整一下reactive函数。

function createReactive(obj, isShallow = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        return target;
      }
      track(target, key);
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果浅层响应,直接返回
      if (typeof res === "object") {
        return reactive(res);
      }
      return res;
    },
    set(target, key, newVal, receiver) {
      const oldVal = target[key]; // 获取旧值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 比较新值和旧值
          trigger(target, key, type);
        }
      }
      return res;
    },
    has(target, key) {
      track(target, key);
      return Reflect.has(target, key);
    },
    ownKeys(target, key) {
      // 这里将副作用函数和唯一标识ITERATE_KEY绑定了
      track(target, ITERATE_KEY);
      return Reflect.ownKeys(target);
    },
    deleteProperty(target, key) {
      // 检查删除的key是否为自身属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (res && hadKey) {
        trigger(target, key, "DELETE");
      }
      return res;
    },
  });
}
function reactive(obj) {
  return createReactive(obj, true);
}
function shallowReactive(obj) {
  return createReactive(obj, false);
}

6.只读和浅只读

  实现只读其实只需要在createReactiv函数中添上第三个参数isReadOnly。

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    set(target, key, newVal, receiver) {
      if (isReadOnly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }
      const oldVal = target[key]; // 获取旧值
      const type = Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD";
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          // 比较新值和旧值
          trigger(target, key, type);
        }
      }
      return res;
    },
    deleteProperty(target, key) {
      if (isReadOnly) {
        console.warn(`属性${key}是只读的`);
        return true;
      }
      // 检查删除的key是否为自身属性
      const hadKey = Object.prototype.hasOwnProperty.call(target, key);
      const res = Reflect.deleteProperty(target, key);
      if (res && hadKey) {
        trigger(target, key, "DELETE");
      }
      return res;
    },
  }
}

  当然,对于设置了只读属性的对象的属性,很明显就没必要添加依赖了,所以对于get也要进行相应的修改.

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通过获取raw属性,拿到初始对象
        return target;
      }
      if (!isReadOnly) {
        // 只读情况下不需要建立联系
        track(target, key);
      }
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果浅层响应,直接返回
      if (typeof res === "object") {
        // 如果获取的值是对象,递归调用reactive函数,得到深层响应对象
        return reactive(res);
      }
      return res;
    },
  }
}

  但是,上述操作只能做到浅只读,深只读实现起来也很简单,判断只读标记然后递归添加只读属性就行了.

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通过获取raw属性,拿到初始对象
        return target;
      }
      if (!isReadOnly) {
        // 只读情况下不需要建立联系
        track(target, key);
      }
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果浅层响应,直接返回
      if (typeof res === "object" && res !== null) {
        // 如果获取的值是对象,且只读标记的值为true,递归调用readonly函数,得到深层只读响应对象.否则,递归调用reactive函数,得到深层响应对象
        return isReadOnly ? readonly(res) : reactive(res);
      }
      return res;
    },

  然后和reactive函数一样,封装一下只读readonly函数.

function readonly(obj) {
  return createReactive(obj, true, true);
}
function shallowReadonly(obj) {
  return createReactive(obj, false, true);
}

7.代理数组

(1)读取和修改操作

数组的读取操作:

  • 通过索引访问元素,arr[0]
  • 访问数组长度,arr.length
  • for in循环访问arr对象
  • for of循环访问arr对象
  • 数组的原型方法,find,concat等

数组的修改操作:

  • 通过索引修改数组,arr[0] = 1
  • 修改数组长度,arr.length = 1
  • 数组的栈、队列方法,arr.push
  • 修改数组的原型方法,arr.slice,arr.sort等

  对于通过索引访问这一操作,它实际上和普通对象是一样的,都可以通过get直接拦截。但是对于通过索引修改这一操作,就稍有不同了,因为如果当前设置的索引>数组长度的话,相应的也会对数组的长度进行修改,而且在修改数组长度的过程中,还需要对数组长度的修改做出响应。同时,直接修改数组的length属性也会造成影响,如果小于当前数组长度,那么会对差值内元素进行清楚操作,否则则对之前的元素没有影响。

  首先我们对应修改数组索引设置这一操作:

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    set(target, key, newVal, receiver) {
      if (isReadOnly) {
        // 如果对象只读,提示报错信息
        console.warn(`属性${key}是只读的`);
        return true;
      }
      const oldVal = target[key]; // 获取旧值
      // 判断操作类型,如果是数组类型,则根据索引大小来判断
      const type = Array.isArray(target)
        ? Number(key) < target.length
          ? "SET"
          : "ADD"
        : Object.prototype.hasOwnProperty.call(target, key)
        ? "SET"
        : "ADD"; // 获取操作类型
      const res = Reflect.set(target, key, newVal, receiver);
      if (target === receiver.raw) {
        if (oldVal !== newVal && (oldVal === oldVal || newVal === newVal)) {
          trigger(target, key, type, newVal); // 添加第四个参数
        }
      }
      return res;
    }
  }
}

  然后修改trigger函数,判断是否为数组和ADD操作,然后添加length属性的相关操作

// trigger函数添加第四个参数newVal,即触发响应的值
function trigger(target, key, type) {
  const depsMap = bucket.get(target); // 首先从对象桶中取出当前对象的依赖表
  if (!depsMap) return;
  const effects = depsMap.get(key); // 从依赖表中拿到当前键值的依赖集合
  const iterateEffects = depsMap.get(ITERATE_KEY); // 尝试获取for in循环操作的依赖集合
  const effectToRun = new Set(); // 创建依赖执行队列
  if (type === "ADD" && Array.isArray(target)) {
    // 如果操作类型是ADD且对象类型是数组,将length相关依赖添加到待执行队列中
    const lengthEffects = depsMap.get("length");
    lengthEffects &&
      lengthEffects.forEach((fn) => {
        if (fn !== activeEffect) {
          effectToRun.add(effectFn);
        }
      });
  }
  if (Array.isArray(target) && key === "length") {
    // 对于索引大于等于新length值的元素,需要将所有相关联的函数取出添加到effectToRun中待执行
    if (key >= newVal) {
      effects.forEach((fn) => {
        if (fn !== activeEffect) {
          effectToRun.add(fn);
        }
      });
    }
  }

(2)数组的遍历

  首先是for in循环,会影响for in循环的操作主要是根据索引设置数组值和修改数组的length属性,而这两种操作,实际上都是对数组length值的操作,因此我们只需要在onwKeys方法里判断,当前操作的是否是数组,如果是数组的话,就使用length属性作为key并建立联系。

ownKeys(target, key) {
      // 这里将副作用函数和唯一标识ITERATE_KEY绑定了
      track(target, Array.isArray(target) ? "length" : ITERATE_KEY); // 进行依赖收集
      return Reflect.ownKeys(target);
},

  然后是for of循环,它主要是通过和索引和length进行操作,所以不需要进行额外的操作,就可以实现依赖。但是在使用for of循环的时候,会对数组的Symbol.iterator属性进行读取,该属性是一个symbol值,为了避免发生意外错误,以及性能上的考虑,需要对类型为了symbol的值进行隔离。

function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通过获取raw属性,拿到初始对象
        return target;
      }
      if (!isReadOnly && typeof key !== "symbol") {
        // 只读情况和key值为symbol的情况下不需要建立联系
        track(target, key);
      }
      const res = Reflect.get(target, key, receiver);
      if (isShallow) return res; // 如果浅层响应,直接返回
      if (typeof res === "object" && res !== null) {
        // 如果获取的值是对象,递归调用reactive函数,得到深层响应对象
        return isReadOnly ? readonly(res) : reactive(res);
      }
      return res;
    },
  }
}

(3)数组的查找方法

  arr.includes方法在正常情况下是可以正常触发绑定的,因为arr.include方法会在查找过程中访问数组对象的length属性和索引。但是在一些特殊的情况下,比如说数组元素是对象的情况下,在我们目前的响应式系统下,就会出现一些特殊的情况。

const obj = {};
const arr = reactive([arr]);
console.log(arr.includes(arr)); // false

  运行上述代码,得到的结果为false,这是因为在我们之前代码设计中,如果读取操作取到的值是一个可代理对象,那么我们会继续对这个对象进行代理。而进行继续代理后,得到的对象就是一个全新的对象了。

if (typeof res === "object" && res !== null) {
  // 如果获取的值是对象,递归调用reactive函数,得到深层响应对象
  return isReadOnly ? readonly(res) : reactive(res);
}

  对此,我们创建一个缓存Map,避免重复创建的问题。

const reactiveMap = new Map();
function reactive(obj) {
  // 获取当前对象的缓存值
  const existionProxy = reactiveMap.get(obj);
  // 如果当前对象存在缓存值,直接返回
  if (existionProxy) return existionProxy;
  // 否则创建新的响应对象
  const proxy = createReactive(obj, true);
  // 缓存新对象
  reactiveMap.set(obj, proxy);
  return proxy;
}

  但是这个时候我们又会碰到一个新问题,就是如果传入原始对象,也就是obj的话,也会返回false,这是因为我们会从arr中拿到的是响应式对象,所以我们需要修改arr.includes的默认行为。

const originMethod = Array.prototype.includes;
const arrayInstrumentations = {
  includes: function (...args) {
    // this是代理对象,先在代理对象中进行查找
    let res = originMethod.apply(this, args);
    if (res === false) {
      // 如果在代理对象上无法找到,再到原始对象上找
      res = originMethod.apply(this.raw, args);
    }
    return res;
  },
};
function createReactive(obj, isShallow = false, isReadOnly = false) {
  return new Proxy(obj, {
    get(target, key, receiver) {
      if (key === "raw") {
        // 通过获取raw属性,拿到初始对象
        return target;
      }
      // 如果操作目标是数组,而且key处于arrayInstrumentations之上,那么返回自定义的行为
      if (Array.isArray(target) && arrayInstrumentations.hasOwnProperty(key)) {
        return Reflect.get(arrayInstrumentations, key, receiver);
      }
    }
  }
}

  除了includes外,需要做类似处理的还有indexof和lastIndexOf

const arrayInstrumentations = {};
["includes", "indexof", "lastIndexof"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    // this 是代理对象,先在代理对象中查找,将结果存储到 res 中
    let res = originMethod.apply(this, args);
    // res 为 false 说明没找到,通过 this.raw 拿到原始数组,再去其中查找,并更新 res 值
    if (res === false || res === -1) {
      res = originMethod.apply(this.raw, args);
    }
    return res;
  };
});

(4)隐式修改数组的方法

  主要有push、pop、shift、unshift和splice,以push为例,push在添加元素的同时,也会读取length属性,而这回导致两个独立的副作用函数相互影响。因此我们也需要重写push操作,来避免这种情况的产生。这里我们添加一个是否进行追踪的标记,在push方法执行之前,将标记置为false

let shouldTrack = true; // 是否进行追踪标记
["push"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false;
    // 默认行为
    let res = originMethod.apply(this, args);
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true;
    return res;
  };
});
function track(target, key) {
  if (!activeEffect || !shouldTrack) {
    // 如果没有当前执行的副作用函数,不进行处理
    return;
  }
}

​ 最后,修改所以该类行为。

["push", "pop", "shift", "unshift", "splice"].forEach((method) => {
  const originMethod = Array.prototype[method];
  arrayInstrumentations[method] = function (...args) {
    // 在调用原始方法之前,禁止追踪
    shouldTrack = false;
    // 默认行为
    let res = originMethod.apply(this, args);
    // 在调用原始方法之后,恢复原来的行为,即允许追踪
    shouldTrack = true;
    return res;
  };
});

到此这篇关于Vue reactive函数实现流程详解的文章就介绍到这了,更多相关Vue reactive函数内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • Vue中的reactive函数操作代码

    reactive函数 之前给大家介绍过vue3.2 reactive函数问题小结,喜欢的朋友点击查看. 作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数,ref函数可以用基本类型也可以对象类型) 语法:const 代理对象= reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象,简称proxy对象) reactive定义的响应式数据是“深层次的”. 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据进行操作. <tem

  • Vue3中的ref和reactive响应式原理解析

    目录 1 ref 2 isref判断是不是一个ref对象 3 shallowref创建一个跟踪自身.value变化的 ref,但不会使其值也变成响应式的 4 triggerRef 5 customRef 6 reactive用来绑定复杂的数据类型 7 readonly 8 shallowReactive 9toRef 10toRefs 11toRaw Vue3系列4--ref和reactive响应式 本节主要介绍了响应式变量和对象,以及变量和对象在响应式和非响应式之间的转换. 1 ref 接受一

  • Vue3中reactive与ref函数使用场景

    目录 前言 简单了解 ref & reactive reactive ref reactive 能做的 ref 也能做,并且还是用 reactive 做的 ref 能做,但是 reactive 不能做 总结 前言 如果你使用过 Vue3,你知道的,在 Vue3 中有两个非常常用的响应式 API:reactive 和 ref.它们会把我们想要追踪的数据变成响应式. 而且我们在使用时一直被告知 ref 用于创建基础类型的响应式,也可以创建引用类型的响应式.而对于引用类型,底层也是转换为 reacti

  • Vue3 reactive响应式赋值页面不渲染的解决

    目录 Vue3 reactive响应式赋值页面不渲染 问题描述 1.因数据结构而导致的未渲染解决方法 2.因页面节点未加载导致页面未渲染解决方法 3.因未指到具体点未渲染解决方法 Vue3 响应式原理-reactive Reactivity模块基本使用 编写reactive函数 Vue3 reactive响应式赋值页面不渲染 问题描述 //声明变量 let data = reactive([]) http().then(res=>{   data = res.data   console.log

  • Vue3中ref和reactive的基本使用及区别详析

    目录 前言 ref—计数器案例 reactive—计数器案例 区别 类似使用ref 类似使用 reactive 附:ref和reative的使用心得 总结 前言 今天给大家讲一下在vue3中ref和reactive的使用方法,以及他们的使用区别在哪里,下面通过一个简单的计数器的例子来给大家进行说明 ref—计数器案例 ref 主要用于基本类型的响应,看如下代码: import { ref } from 'vue' // 导入ref interface DataProps { count: num

  • Vue reactive函数实现流程详解

    目录 1.Reflect 2.Proxy的工作原理 3.代理Object 4.合理的触发响应 5.深响应和浅响应 6.只读和浅只读 7.代理数组 1.Reflect   Proxy有着可以拦截对对象各种操作的能力,比如最基本的get和set操作,而Reflect也有与这些操作同名的方法,像Reflect.set().Reflect.get(),这些方法和它们所对应的对象基本操作完全一致. const data = { value: '1', get fn() { console.log(this

  • Vue编译优化实现流程详解

    目录 动态节点收集与补丁标志 1.传统diff算法的问题 2.Block和PatchFlags 3.收集动态节点 4.渲染器运行时支持 5.Block树 静态提升 预字符化 缓存内联事件处理函数 v-once 动态节点收集与补丁标志 1.传统diff算法的问题 对于一个普通模板文件,如果只是标签中的内容发生了变化,那么最简单的更新方法很明显是直接替换标签中的文本内容.但是diff算法很明显做不到这一点,它会重新生成一棵虚拟DOM树,然后对两棵虚拟DOM树进行比较.很明显,与直接替换标签中的内容相

  • Vue h函数的使用详解

    目录 一.认识 二.使用 1.h() 参数 2.简单的使用 3.实现一个计数器案例 4.函数组件和插槽的使用 三.jsx的使用 1.jsx的认识 2.下载Babel插件支持vue(现在貌似脚手架直接支持) 3.配置babel 4.简单的使用 5.计数器案例 6.组件和插槽的使用 一.认识 文档:https://v3.cn.vuejs.org/guide/render-function.html#dom-%E6%A0%91 ​ h() 到底会返回什么呢?其实不是一个实际的 DOM 元素.它更准确的

  • VUE render函数使用和详解

    目录 前言 render的作用 render函数讲解 render和template的区别 render举例 总结 前言 在平时编程时,大部分是通过template来创建html.但是在一些特殊的情况下,使用template方式时,就无法很好的满足需求,在这个时候就需要 通过JavaScript 的编程能力来进行操作.此时,就到了render函数展示拳脚去时候了. render的作用 官网示例入口 在官网的这里示例中,使用组件,将相同的内容通过solt放进h1-h6的标签中,在使用传统方式时,代

  • Vue登录功能的实现流程详解

    目录 Vue项目中实现登录大致思路 安装插件 创建store 封装axios qs vue 插件 api.js的作用 路由拦截 登录页面实际使用 Vue项目中实现登录大致思路 1.第一次登录的时候,前端调后端的登陆接口,发送用户名和密码 2.后端收到请求,验证用户名和密码,验证成功,就给前端返回一个token 3.前端拿到token,将token存储到localStorage和vuex中,并跳转路由页面 4.前端每次跳转路由,就判断 localStroage 中有无 token ,没有就跳转到登

  • Vue echarts模拟后端数据流程详解

    目录 KOA2的使用 安装 Koa app.js入口文件 KOA2的使用 KOA2是由Express 原班人马打造. 环境依赖 Node v7.6.0 及以上. 支持 async 和 await 洋葱模型的中间件 写响应函数(中间件) 响应函数是通过use的方式才能产生效果, 这个函数有两个参数, ctx :上下文, 指的是请求所处于的Web容器,我们可以通过 ctx.request 拿到请求对象, 也可 以通过 ctx.response 拿到响应对象 next :内层中间件执行的入口 模拟服务

  • vue cli实现项目登陆页面流程详解

    目录 1. 搭建项目 1.1 使用vue-cli创建项目 1.2 通过npm安装element-ui 1.3 导入组件 2 创建登录页面 2.1 创建登录组件 2.2 引入css(css.txt) 2.3 配置路由 2.4 在Login组件中将提交按键调整为100%宽度 2.5 运行效果 3. 后台交互 3.1 引入axios 3.2 axios/qs/vue-axios安装与使用 3.2.1 安装axios 3.2.2 发送get请求 3.2.3 发送post请求 3.2.4 简化axios使

  • Vue生命周期与后端交互实现流程详解

    目录 表单控制 购物车案例 v-model进阶(了解) vue生命周期 与后端交互 电影案例 表单控制 1.input:checkbox(单选,多选),radio(单选) 2.代码展示 <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Title</title> <script src="j

  • vue中各选项及钩子函数执行顺序详解

    在vue中,实例选项和钩子函数和{{}}表达式都是不需要手动调用就可以直接执行的. vue的生命周期如下图: 在页面首次加载执行顺序有如下: beforeCreate //在实例初始化之后.创建之前执行 created //实例创建后执行 beforeMounted //在挂载开始之前调用 filters //挂载前加载过滤器 computed //计算属性 directives-bind //只调用一次,在指令第一次绑定到元素时调用 directives-inserted //被绑定元素插入父

  • Vue openLayers实现图层数据切换与加载流程详解

    目录 openlayers介绍 一.实现效果预览 二.代码实现 openlayers介绍 OpenLayers是一个用于开发WebGIS客户端的JavaScript包.OpenLayers 支持的地图来源包括Google Maps.Yahoo. Map.微软Virtual Earth 等,用户还可以用简单的图片地图作为背景图,与其他的图层在OpenLayers 中进行叠加,在这一方面OpenLayers提供了非常多的选择.OpenLayers采用面向对象方式开发. OpenLayers 是一个专

随机推荐