Vue3响应式原理设计和实现

响应式

什么是响应式

响应式是一个过程,这个过程存在两个参与者:一方触发,另一方响应。所谓响应式的两个参与者:

  • 触发者:数据
  • 响应者:引用该数据的函数(也叫副作用函数)

手动响应式

当数据改变时,引用该数据的函数响应数据的改变,重新执行。举一个最简单的手动响应式过程的例子:

<div id="app"></div>
const obj = {
    
    name: 'Jane'}

const effect = () => {
    
    
  app.innerHTML = obj.name
}

effect()

setTimeout(() => {
    
    
  obj.name = 'little Jane'
  // 手动执行副作用函数
  effect()
}, 1000)

其中,data 就是触发者数据,effect() 就是响应者,也叫副作用函数。

proxy代理对象

new Proxy 传入一个源对象,返回一个新对象(即代理对象),后续对代理对象的操作,都可以自定义访问过程。

const obj = {
    
    name: 'Jane'}
const proxy = new Proxy(obj, {
    
    
  get(target, key) {
    
    
    // 当访问代理对象的属性时执行 get 函数
    return target[key]
  },
  set(target, key, value) {
    
    
    // 当设置代理对象的属性时执行 set 函数
    target[key] = value
    return true
  }
})

其中,proxy 就是代理对象, target 是源对象,所以访问代理对象属性时其实返回的还是源对象内容,同样,对代理对象的属性操作其实最终还是操作的是源对象的属性。通过访问代理对象,可以自定义访问过程

响应式系统

一个属性注册一个副作用函数

通过 proxy代理对象,可以通过 proxy 原理实现一个 reactive 函数,用来将普通对象转化为 proxy 代理对象,并结合响应式系统:

const isObject = value => {
    
    
  return typeof value === 'object' && value !== null
}
const reactive = obj => {
    
    
  if(!isObject(obj)) return
  return new Proxy(obj, {
    
    
    get(target, key) {
    
    
      return target[key]
    },
    set(target, key, value) {
    
    
      target[key] = value
      effect() // 当设置 state.name 时,重新执行副作用函数
      return true
    }
  })
}

const state = reactive({
    
    name: 'Jane'})
const effect = () => {
    
    
  app.innerHTML = state.name
}
effect()
setTimeout(() => {
    
    
  state.name = 'little Jane'
}, 1000)

数据的改变,触发关联的副作用函数重新执行,通过 Proxy 代理源数据,在 Proxy 的自定义 set 操作中,重新执行副作用函数。

一个属性注册多个副作用函数

上面的响应式系统是一对一的数据和副作用函数关系。但是,理论上当属性改变时,属性关联的每一个副作用函数的都应该重新执行,这里 state.name 只被 effect 这一个函数引用了,但是如果有多个函数都引用了 state.name 的话,那这多个副作用函数都需要被重新执行。同时,为了避免副作用函数的重复,这里使用 set 类型来存放副作用函数集合:

const bucket = new Set() // 副作用函数桶,用来存放所有的副作用函数
const isObject = value => {
    
    
  return typeof value === 'object' && value !== null
}
const reactive = obj => {
    
    
  if(!isObject(obj)) return
  return new Proxy(obj, {
    
    
    get(target, key) {
    
    
      return target[key]
    },
    set(target, key, value) {
    
    
      target[key] = value
      bucket.forEach(fn => fn())
      return true
    }
  })
}

const state = reactive({
    
    name: 'Jane'})

const effect = () => {
    
    
  console.log('effect 执行')
}
bucket.add(effect)

const effect1 = () => {
    
    
  console.log('effect1 执行')
}
bucket.add(effect1)

// 触发 set,在 set 可以自定义执行所有副作用函数
setTimeout(() => {
    
    
  state.name = 'little Jane'
}, 1000)

多个属性注册不同的副作用函数

state 有多个属性时(例如 nameage 两个属性),不同的属性发生变化时应该执行对应的副作用函数,而不是执行所有数据的所有副作用函数。需要修改副作用函数桶的结构:
在这里插入图片描述

扫描二维码关注公众号,回复: 15004899 查看本文章
const bucket = new Map() // 副作用函数桶,用来存放所有的副作用函数
let activeEffect = null
const isObject = value => {
    
    
  return typeof value === 'object' && value !== null
}

// 收集依赖
const track = (target, key) => {
    
    
  if(!activeEffect) return
  let depSet = bucket.get(key)
  if(!depSet) {
    
    
    depSet = new Set()
    bucket.set(key, depSet)
  }
  depSet.add(activeEffect)
}

// 触发副作用函数
const trigger = (target, key) => {
    
    
  let depSet = bucket.get(key)
  if(depSet) {
    
    
    depSet.forEach(fn => fn())
  }
}

const reactive = obj => {
    
    
  if(!isObject(obj)) return
  return new Proxy(obj, {
    
    
    get(target, key) {
    
    
      // get 操作时收集依赖
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
    
    
      target[key] = value
      // set 操作时,触发副作用函数执行
      trigger(target, key)
      return true
    }
  })
}

const effect = fn => {
    
    
  if(typeof fn !== 'function') return
  activeEffect = fn
  fn()
  activeEffect = null
}

const state = reactive({
    
    name: 'Jane', age: 25})

effect(() => {
    
    
  console.log('get执行', state.name)
})
effect(() => {
    
    
  console.log('get执行', state.age)
})

console.log(bucket)

在这里插入图片描述

多个数据不同属性注册不同的副作用函数

有多个 state 数据时,它们可能有不同属性名,但是也可能会有相同属性名
在这里插入图片描述

let activeEffect = null
let bucket = new WeakMap() // [state -> Map[name -> Set(fn, fn), age -> Set(fn, fn)], state1 -> Map]

const isObject = value => {
    
    
  return typeof value === 'object' && value !== null
}

// 收集依赖
const track = (target, key) => {
    
    
  if(!activeEffect) return
  let depMap = bucket.get(target)
  if(!depMap) {
    
    
    depMap = new Map()
    bucket.set(target, depMap)
  }
  let depSet = depMap.get(key)
  if(!depSet) {
    
    
    depSet = new Set()
    depMap.set(key, depSet)
  }
  depSet.add(activeEffect)
}

// 触发依赖
const trigger = (target, key) => {
    
    
  let depMap = bucket.get(target)
  if(!depMap) return
  let depSet = depMap.get(key)
  if(depSet) {
    
    
    depSet.forEach(fn => fn())
  }
}

const reactive = obj => {
    
    
  if(!isObject(obj)) return
  return new Proxy(obj, {
    
    
    get(target, key) {
    
    
      // get 操作时收集依赖
      track(target, key)
      return target[key]
    },
    set(target, key, value) {
    
    
      target[key] = value
      // set 操作时,触发副作用函数执行
      trigger(target, key)
      return true
    }
  })
}

const effect = fn => {
    
    
  if(typeof fn !== 'function') return
  activeEffect = fn
  fn()
  activeEffect = null
}

const state1 = reactive({
    
    name: 'Jane', age: 25})
const state2 = reactive({
    
    name: 'Qu', age: 27})

effect(() => {
    
    
  console.log('我在拿 state1.name...', state1.name)
})
effect(() => {
    
    
  console.log('我在拿 state1.age...', state1.age)
})
effect(() => {
    
    
  console.log('我在拿 state2.name......', state2.name)
})
effect(() => {
    
    
  console.log('我又拿 state2.name......', state2.name)
})

console.log(bucket)

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_43443341/article/details/128304893