200 行从零实现 vue3
emmm 用半天时间捋顺了 vue3 的源码,再用半天时间写了个 mini 版……
我觉得我也是没谁了,vue3 的源码未来一定会烂大街的,我们越早的去复现它,就……emm可以越早的装逼hhh
源码
关于源码解读,我就不写文章了,未来肯定会很多,但是为了方便理解,我们可以事先写一下伪代码
get => track(依赖收集)=> set => trigger(批量执行 effect)=> diff + patch结合上面这一行,我们可以很轻松的实现一个mini vue3
实现
首先实现 mount
export function mount (instance,el) {
let mounted = false
instance.render = instance.setup()
instance.update = effect(function componentEffects () {
if (!mounted) {
el.appendChild(createElement(instance.render()))
mounted = true
} else {
const oldVnode = instance.subTree || null
const newVnode = (instance.subTree = instance.render())
// 忽略 diff patch
el.innerhtml = ''
el.appendChild(createElement(newVnode))
}
})
instance.update()
}
mount 方法传入一个对象,对象里有一个 setup 函数,它这么用:
const App = {
setup () {
let state = reactive({ count: 0 })
const add = () => state.count++
return () => <button onClick={add}>{state.count}</button>
}
}
createApp().mount(App) //忽视 createApp
我们看到一个神奇的秘密,就是 setup 不是直接返回 jsx,它返回的是一个函数,这很重要,这里是个闭包,相当于缓存了 this
我们补全一些 dom 的方法:
export function updateProperty (dom, name, oldValue, newValue) {
if (name === 'key') {
} else if (name[0] === 'o' && name[1] === 'n') {
name = name.slice(2).toLowerCase()
if (oldValue) dom.removeEventListener(name, oldValue)
dom.addEventListener(name, newValue)
}
}
export function createElement (vnode) {
let dom = vnode.tag === TEXT ? document.createTextNode(vnode.type) : document.createElement(vnode.type)
if (vnode.children) {
for (let i = 0; i < vnode.children.length; i++) {
dom.appendChild(createElement(vnode.children[i]))
}
}
for (var name in vnode.props) {
updateProperty(dom, name, null, vnode.props[name])
}
return dom
}
这样我们就可以点击按钮了,点击按钮,会触发 set,进而触发 trigger 函数
export function trigger (target, key) {
let deps = targetMap.get(target)
const effects = new Set()
deps.get(key).forEach(e => effects.add(e))
effects.forEach(e => e())
}
这个方法很简单,就是批零执行 effect,还记得 effect 是啥吗?是我们一开始缓存的 update 函数
但是这里的 effects 是从 targetMap 中拿到的,这玩意从哪里来?
答案就是依赖收集,也就是 track 函数
export function track (target, key) {
const effect = activeEffectStack[activeEffectStack.length - 1]
if (effect) {
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
let dep = depsMap.get(key)
if (!dep) {
depsMap.set(key, (dep = new Set()))
}
if (!dep.has(effect)) {
dep.add(effect)
}
}
}
track 做的事情也很简单,就是初始化各种的 Map ,Set等,顺便将 effect 添加进去,targetMap 的结构长这样:

用代码描述,差不多是这样的:
WeakMap
target Map
key Set
effect1, effect2,... 最后补全 reactivity 的内容,这部分就是纯 proxy 了,不难:
const toProxy = new WeakMap()
const toRaw = new WeakMap()
export const targetMap = new WeakMap()
const isObj = obj => typeof obj === 'object'
export function reactive (target) {
if (!isObj(target)) return target
let proxy = toProxy.get(target)
if (proxy) return proxy
if (toRaw.has(target)) return target
const handlers = {
get (target, key, receiver) {
let res = Reflect.get(target, key, receiver)
if (isObj(target[key])) {
return reactive(res)
}
track(target, key)
return res
},
set (target, key, value, receiver) {
let res = Reflect.set(target, key, value, receiver)
if (key in target) {
trigger(target, key)
}
return res
},
deleteProperty () {
return Reflect.defineProperty(target, key)
}
}
let observed = new Proxy(target, handlers)
toProxy.set(target, observed)
toRaw.set(observed, target)
if (!targetMap.has(target)) {
targetMap.set(target, new Map())
}
return observed
}
以上,我们成功复现了 vue3 核心,包括了响应式,依赖收集,触发更新等
讲道理,仅仅是原理的话,真的很容易实现,里面没啥复杂的地方,其实这类框架中,最复杂的还是 diff 算法,但是今天我们的目标是搞清楚 vue3 的基本套路
总结
我开了个新坑,叫 voe,会结合 vue 的给出对等的复现,github 在这里:https://github.com/132yse/voe
但是我不准备完全复现 vue3,我认为 vue3 在业务层面已经登峰造极了,没理由替代它
voe 未来会寻找新的突破,比如双线程,web worker 等
复现 vue 只是顺手的事~
原文:https://zhuanlan.zhihu.com/p/91266204
本文内容仅供个人学习、研究或参考使用,不构成任何形式的决策建议、专业指导或法律依据。未经授权,禁止任何单位或个人以商业售卖、虚假宣传、侵权传播等非学习研究目的使用本文内容。如需分享或转载,请保留原文来源信息,不得篡改、删减内容或侵犯相关权益。感谢您的理解与支持!