Vue3响应式原理
「时光不负,创作不停,本文正在参加2021年终总结征文大赛」
前言
前端菜鸟在学习Vue3时的总结输出,若有错误或者不足的地方,还请各位大佬们多多指正,不吝赐教。
本文主要对于Vue3响应式原理的总结,在进行响应式原理学习之前,首先要弄清出什么是响应式。
什么是响应式
有一个初始化的值,有一段代码使用了这个值,当这个值发生变化时,这段代码也自动重新执行,这种可以自动响应数据变化的代码机制,称为响应式 我们看一段代码:
let a = 10
let b = a + 10
console.log(b) // b=20
a = 20
//预期达到的结果b = 30
console.log(b) // 实际b = 20
想让变量 b
跟变量a
的变化发生变化,我们应该怎么做呢?
-
let a = 10 let b = a + 10 console.log(b) // b=20 a = 20 //在对b变量进行一次赋值操作 b = a + 10 console.log(b) // b = 30
-
let a = 10 let b update() // b=20 a = 20 //封装一个update函数 function update(){ return b = a + 10 console,log(b) } update() //b=30
- 变量
a
发生了变化,我希望变量b
可以自动发生变化,这个时候我们可以安装vue的响应式模块(注:Vue库被拆分多个模块,每个模块可以单独下载使用)npm install @vue/reactivity
const {reactive,effect} = require("@vue/reactivity") //首先声明一个响应式变量a let a = reactive({ value:1 }) let b //监听 effect(()=>{ //传入一个函数作为参数 //会先执行一次这个函数 b = a.value + 10 console.log(b }) //a的响应式对象的值发生改变后 effect 会在执行一次 a.value = 10
上面第三种方法就是Vue的响应式方法,下面我们就尝试自己实现应式的方法
Vue响应式的实现
定义一个响应式函数
实现响应式的一个关键就是响应式函数watchEffect的实现,凡是传入watchEffect的函数,就是需要响应式的,其他默认定义的函数时不需要响应式的
const effects = new Set() //用来收集需要函数
function watchEffect(effect){
effects.push(effect)
effect() //执行需要响应式的函数
}
响应式依赖的收集
目前我们收集的依赖是放到一个数组中来保存,但是这会存在数据管理的问题
- 我们在实际开发中需要监听的是很多对象的响应式
- 这些对象需要监听的不只是一个属性,他们很多属性的变化,都会有对应的响应式函数
- 我们不可能在全局维护一大堆的数组来保存这些相应是函数
所以我们要设计一个类,用这个类来管路某一个对象的某一个属性的所有响应式函数,相当于代替了原来简单的effects的数组
class Depend {
constructor(){
this.effects = new Set() //依赖不能重复
}
// 收集依赖
depend(effect)
this.effects.add(Effect)
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
}
}
怎样将收集响应式依赖的函数和管理依赖的类串联起来
接下来我们考虑一个问题 如何将响应式响应式函数添加到我们定义的类中间呢?我们可以在全局定义一个中间变量currentEffect
进行传值,代码优化如下:
let currentEffect = null
// 依赖的类
class Depend {
constructor(){
this.effects = new Set() //依赖不能重复
}
// 收集依赖
depend(){
if (currentEffect) {
this.effects.add(currentEffect);
}
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
})
}
}
//收集响应式依赖的函数
function watchEffect(effect){
currentEffect = effect
effect()
currentEffect = null
}
vue3 ref的简单实现
接下来就可以简单实现类似ref变量
let currentEffect = null
// 依赖的类
class Depend {
constructor(val){
this.effects = new Set() //依赖不能重复
this._val =val
}
get value() {
// 访问对象一个属性 会触发get函数 --在这里面可以自动收集依赖
return this._val;
}
set value(newVal) {
this._val = newVal;
// 访问对象一个属性 会触发set函数 --在这里面可以自动触发依赖
}
// 收集依赖
depend(){
if (currentEffect) {
this.effects.add(currentEffect);
}
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
})
}
}
//定义一个dep
const dep = new Depend(10)
//收集响应式依赖的函数
function watchEffect(effect){
currentEffect = effect
effect()
dep.depend()
currentEffect = null
}
let b;
watchEffect(() => {
b = dep.value + 10;
console.log(b);
});
dep.value = 20
dep.notify()
运行结果如下
响应式效果已经出来了,但是响应式函数的收集和响应都是我们手动触发和响应的,我们应该思考能不能自动进行依赖的收集和响应?
我们在dep.value
的访问和修改值时候会触发getter
和setter
函数,所以我们可以在getter
和setter
函数中进行依赖的收集和响应,代码如下
let currentEffect = null
// 依赖的类
class Depend {
constructor(val){
this.effects = new Set()
this,_val = val
}
get value() {
// 访问对象一个属性 会触发get函数 --在这里面可以自动收集依赖
this.depend()
return this._val;
}
set value(newVal) {
this._val = newVal;
// 访问对象一个属性 会触发set函数 --在这里面可以自动触发依赖
this.notify()
}
// 收集依赖
depend(){
this.effects.add(currentEffect)
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
})
}
}
//定义一个dep
const dep = new Depend(10)
//收集响应式依赖的函数
function watchEffect(effect){
currentEffect = effect
effect()
currentEffect = null
}
let b;
watchEffect(() => {
b = dep.value + 10;
console.log(b);
});
reactive的简单实现
目前我们是创建一个dep对象,用来管理对于一个简单数据变化需要建统的响应函数,但实际开发中我们会有不同的对象,另外会有不同的属性需要管理,所以我们需要一中数据结构来管理不同的对象的不同依赖
我们可以写一个getDepend函数专门管理这种依赖关系
const targetMap = new WeakMap() //存储所有
function getDepend(traget,key){
let objMap = targetMap.get(target)
if(!objMap) {
objMap = new Map()
targetMap.set(target,objMap)
}
let dep = objMap.get(key)
if(!dep){
dep = new Dep()
objMap.set(key,dep)
}
接下来我们需要监听对象的变化
- 方式一:通过Object.defineProperty的方式 注:Object.defineProperty方法的使用可以参照Mdn文档
- 方式二:通过new Proxy的方式注:Proxy的使用可以参展Mdn文档
function reactive(obj){
return new Proxy(obj,{
get(target,key,recriver)
cosnt dep = getDepend(target,key) //获取依赖
dep.edpend() //收集依赖
return Reflect.get(target,key,receiver)
}
set(target,key,newVal,receiver){
cosnt dep = getDepend(target,key) //获取依赖
Reflect.set(target,key,newVal,receiver)
dep.notify() //触发依赖要在reflect之后 在之前触发还没来得及修改值
}
}
综合代码并举例
let currentEffect = null
// 依赖的类
class Depend {
constructor(){
this.effects = new Set()
}
// 收集依赖
depend(){
if (currentEffect) {
this.effects.add(currentEffect);
}
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
})
}
}
//收集依赖的函数
function watchEffect(effect){
currentEffect = effect
effect()
currentEffect = null
}
// 封装的获取依赖函数
const targetMap = new WeakMap() //存储所有
function getDepend(traget,key){
let objMap = targetMap.get(target)
if(!objMap) {
objMap = new Map()
targetMap.set(target,objMap)
}
let dep = objMap.get(key)
if(!dep){
dep = new Depend()
objMap.set(key,dep)
}
return dep
}
//创建响应式对象函数
function reactive(obj){
return new Proxy(obj,{
get(target,key,receiver)
const dep = getDepend(target, key) //获取依赖
dep.depend() //收集依赖
return Reflect.get(target,key,receiver)
}
set(target,key,newVal,receiver){
const dep = getDepend(target, key) //获取依赖
Reflect.set(target,key,newVal,receiver)
dep.notify() //触发依赖要在reflect之后 在之前触发还没来得及修改值
})
}
var user = reactive({
age: 19,
});
let double;
watchEffect(() => {
console.log("-----reactive-----");
double = user.age * 2;
console.log(double);
});
user.age = 20;
结果如下图
代码总结
//ref->
let currentEffect = null
// 依赖的类
class Depend {
constructor(val){
this.effects = new Set()
this,_val = val
}
get value() {
// 访问对象一个属性 会触发get函数 --在这里面可以自动收集依赖
this.depend()
return this._val;
}
set value(newVal) {
this._val = newVal;
// 访问对象一个属性 会触发set函数 --在这里面可以自动触发依赖
this.notify()
}
// 收集依赖
depend(){
this.effects.add(currentEffect)
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
})
}
}
//定义一个dep
const dep = new Depend(10)
//收集响应式依赖的函数
function watchEffect(effect){
currentEffect = effect
effect()
currentEffect = null
}
let b;
watchEffect(() => {
b = dep.value + 10;
console.log(b);
});
//reatctive->
let currentEffect = null
// 依赖的类
class Depend {
constructor(){
this.effects = new Set()
}
// 收集依赖
depend(){
if (currentEffect) {
this.effects.add(currentEffect);
}
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
})
}
}
//收集依赖的函数
function watchEffect(effect){
currentEffect = effect
effect()
currentEffect = null
}
// 封装的获取依赖函数
const targetMap = new WeakMap() //存储所有
function getDepend(traget,key){
let objMap = targetMap.get(target)
if(!objMap) {
objMap = new Map()
targetMap.set(target,objMap)
}
let dep = objMap.get(key)
if(!dep){
dep = new Depend()
objMap.set(key,dep)
}
return dep
}
//创建响应式对象函数
function reactive(obj){
return new Proxy(obj,{
get(target,key,receiver)
const dep = getDepend(target, key) //获取依赖
dep.depend() //收集依赖
return Reflect.get(target,key,receiver)
}
set(target,key,newVal,receiver){
const dep = getDepend(target, key) //获取依赖
Reflect.set(target,key,newVal,receiver)
dep.notify() //触发依赖要在reflect之后 在之前触发还没来得及修改值
})
}
//reactive 举例
var user = reactive({
age: 19,
});
let double;
watchEffect(() => {
console.log("-----reactive-----");
double = user.age * 2;
console.log(double);
});
user.age = 20;
vue2的实现
let currentEffect = null
// 依赖的类
class Depend {
constructor(){
this.effects = new Set()
}
// 收集依赖
depend(){
if (currentEffect) {
this.effects.add(currentEffect);
}
}
//响应依赖
notify(){
this.effects.forEach(effect=>{
effect()
})
}
}
//收集依赖的函数
function watchEffect(effect){
currentEffect = effect
effect()
currentEffect = null
}
// 封装的获取依赖函数
const targetMap = new WeakMap() //存储所有
function getDepend(traget,key){
let objMap = targetMap.get(target)
if(!objMap) {
objMap = new Map()
targetMap.set(target,objMap)
}
let dep = objMap.get(key)
if(!dep){
dep = new Depend()
objMap.set(key,dep)
}
return dep
}
//创建响应式对象函数
function reactive(obj){
Object.keys(obj).forEach(key=>{
let value = obj[key]
Object.defineProperty(obj, key, {
get(){
const dep = getDepend(obj,key)
dep.depend()
return value
},
set(newVal){
value = newVal
const dep = getDepend(obj,key)
dep.notify()
}
return obj
}