Vue3设计思路 -- 响应式API

86 阅读3分钟

app.mount('#app')

  • 因为vue3的响应式是基于Proxy的,所以为了这套更优雅的代码也不得不做出让步,没有办法兼容不支持Proxy的浏览器,如果浏览器不支持Proxy,就只能使用Vue2了

响应式实现的过程及原理

首先要明白的是能实现响应式其实靠的就是Proxy,其可以让我们知道何时触发了获取和修改事件。

几个关键的名词
  • effect 副作用函数

  • track 绑定值与副作用函数

  • trigger指定值的所有副作用函数执行

逻辑链路

响应式本质上是有在内存中存一种数据结构,类似于{state:{title:[effect,effect]}},其实efftec就是一个个的可以触发页面更新的函数,只要当这里的title更新了,就去遍历title对应的副作用数组,分别去触发即可实现页面的响应式更新

依赖收集与触发

整体思路就是:第一次初始化的时候就会触发第一次的effecteffect触发页面第一次更新,因为是proxy数据第一次更新只要有拿响应式对象的值就会触发track(依赖收集),之后我们修改响应式对象的值又会触发Proxysetset再触发trigger更新页面页面数据

非常的绕,但是多看代码多理解几次就能理解了,要手写下来确实是需要有很深的功力,我是肯定写不下来的,只能跟着视频操作边理解变操作的写下来,确实对依赖收集的概念有了很深的理解

上代码 – 这里主要要从reactive函数开始看

function reactive(obj) {

const proxy = new Proxy(obj, {

get(target, key) {

/*

render函数第一次渲染的时候会获取引用的值 所以第一次渲染就会走到这个 get 方便我们开始做依赖收集

*/

track(target, key) // 触发依赖收集

return target[key]

},

set(target, key, value) {

target[key] = value

console.log('proxy', key)

/*

修改的时候,去依赖收集的数据结构中 找到对应的key 把 key 对应的 更新函数都执行一遍,这就触发了页面的响应式

*/

trigger(target, key)

// app.update()

}

/*

proxy 的优势出了可以监听get 和 set之外 还是可以监听另外十多种情况,这个Object.defineProperty()做不到的

这个是语言层面上的升级

*/

})

return proxy

}

// 副作用函数 -- 这里是为了暂时的存放每一个副作用函数,当副作用函数已经和依赖关系建立好了之后再清除空间

const effectStack = []

function effect(fn) {

const eff = function () {

try {

effectStack.push(eff)

fn() // 执行 最重要的 render函数 页面才更新

} finally {

effectStack.pop()

}

}

eff()

return eff

}

// 依赖收集函数

/*

依赖收集的数据结构是这样的

{

state:{

title:[effect,effect] // effect 对应的就是一个个的更新函数,每个响应式对象的值都是存着更新函数的数组,当这个值变化的时候遍历执行数组里面的一个个effect函数

}

}

*/

const trackMap = {}

function track(target, key) {

/*

effectStack 是临时存放副作用函数的地方,每次关系建立完成之后就会再清空掉,所以获取数组的最后一个元素即可

*/

const effect = effectStack[effectStack.length - 1]

if (effect) {

let map = trackMap[target]

if (!map) {

map = trackMap[target] = {}

}

let deps = map[key]

if (!deps) {

deps = map[key] = []

}

// 将副作用函数放入 deps

if (deps.indexOf(effect) === -1) {

deps.push(effect)

}

}

}

// 触发 指定的 target 下指定的key 下所有的 副作用函数都执行一遍

function trigger(target, key) {

// 数据结构类似 {state:{title:[effect,effect]}}

const map = trackMap[target]

if (map) {

const effects = map[key]

effects.forEach(effect => effect())

}

}

function createApp(config) {

let app = {}

app.mount = function (id) {

this.rootDOM = document.getElementById(id)

if (!config.render) {

config.render = this.compile(this.rootDOM.innerHTML)

}

if (config.setup) {

this.setupCtx = config.setup()

} else {

this.dataCtx = config.data()

}

this.proxy = new Proxy(this, {

get(target, key) {

if (key in target.setupCtx) {

return target.setupCtx[key]

} else {

return target.dataCtx[key]

}

},

set(target, key, value) {

if (key in target.setupCtx) {

target.setupCtx[key] = value

} else {

target.dataCtx[key] = value

}

}

})

// update 函数就是指定的 副作用函数

this.update = effect(() => {

// 这里会去 proxy 拿值 拿值就会触发 get get触发 收集依赖 这样就将整个环节顺滑的串联起来了

const el = config.render.call(this.proxy)

this.rootDOM.innerHTML = ''

this.rootDOM.appendChild(el)

})

this.update()

return this

}

app.compile = function (template) {

return function () {

const h3 = document.createElement('h3')

h3.textContent = this.title

return h3

}

}

return app

}

总结

vue2的响应式和vue3的响应式的区别