vue3使用这么久,也做了项目,但仍然对一些深层不太了解,遂找到几个狐朋狗友一起撸源码
本章作为vue3源码的大门,我和vue3源码也就此展开一段历程,望掘宝们也能工作顺利
vue3响应式核心
reactive
proxy
reactive实现
effect
track<依赖收集>
trigger<依赖触发>
代码块
结语
reactive
proxy
proxy可以简单理解为一个拦截器,可以对外界操作进行拦截层的过滤/改写等操作
ES6提供Proxy构造函数,即用于生成Proxy实例进行后续使用
const proxy = new Proxy(target, handler)
其中target指的是需要拦截的目标对象,handler定义拦截行为,无操作等同于直接操作原对象
reactive实现
先看下代码基本结构,响应式最重要的就是依赖收集和依赖触发,还需和effect搭配使用
export function reactive(raw: any) {
return new Proxy(raw, {
get(target, key) {
const res = Reflect.get(target, key)
// 依赖收集
track(target, key)
return res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
// 触发依赖
trigger(target, key)
return res
}
})
}
这里为什么不用 key[value] 呢,在代理对象中使用 Reflect 才能得到一直符合期望的值<展开太多,可自行百度>
effect
class ReactiveEffect {
private _fn: any
constructor(fn) {
this._fn = fn
}
run() {
this._fn()
}
}
export function effect(fn) {
// fn
const _effect = new ReactiveEffect(fn)
_effect.run()
}
基本的副作用函数也实现了,下面可以实现track及trigger
track
class ReactiveEffect {
private _fn: any
constructor(fn) {
this._fn = fn
}
run() {
// 全局变量收集effect fn
activeEffect = this
this._fn()
}
}
const targetMap = new WeakMap()
export function track(target, key) {
const depMap = targetMap.get(target)
if (!depMap) {
const depMap = new Map()
targetMap.set(target, depMap)
}
const dep = depMap.get(key)
if (!dep) {
const dep = new Set()
depMap.set(key, dep)
}
}
// 全局变量收集effect fn
let activeEffect
export function effect(fn) {
// fn
const _effect = new ReactiveEffect(fn)
_effect.run()
}
targetMap数据格式
关系一层层建立起来的,dep存放起来,使用取出即可
trigger
依赖已经收集起来,只需要用trigger执行即可
export function trigger(target, key) {
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
for (const effect of dep) {
effect.run()
}
}
基本的依赖收集,依赖触发就完成了,即实现了vue3的响应式的核心源码
代码块
effect.ts
class ReactiveEffect {
private _fn: any
constructor(fn) {
this._fn = fn
}
run() {
// 全局变量收集effect fn
activeEffect = this
this._fn()
}
}
const targetMap = new WeakMap()
export function track(target, key) {
// target -> key -> dep
let depsMap = targetMap.get(target)
// 初始化判断
if (!depsMap) {
depsMap = new Map()
targetMap.set(target, depsMap)
}
let dep = depsMap.get(key)
if (!dep) {
dep = new Set()
depsMap.set(key, dep)
}
dep.add(activeEffect)
}
export function trigger(target, key) {
let depsMap = targetMap.get(target)
let dep = depsMap.get(key)
for (const effect of dep) {
effect.run()
}
}
// 全局变量收集effect fn
let activeEffect
export function effect(fn) {
// fn
const _effect = new ReactiveEffect(fn)
_effect.run()
}
reactive.ts
import { track, trigger } from './effect'
export function reactive(raw: any) {
return new Proxy(raw, {
get(target, key) {
const res = Reflect.get(target, key)
// 依赖收集
track(target, key)
return res
},
set(target, key, value) {
const res = Reflect.set(target, key, value)
// 触发依赖
trigger(target, key)
return res
}
})
}
补充<优化effect>
我们在调用effect时,返回一个runner函数,它可以拿到我们传给effect的fn,然后调用fn获取它的返回值
class ReactiveEffect {
private _fn: any
constructor(fn) {
this._fn = fn
}
run() {
// 全局变量收集effect fn
activeEffect = this
return this._fn()
}
}
// 全局变量收集effect fn
let activeEffect
export function effect(fn) {
// fn
const _effect = new ReactiveEffect(fn)
_effect.run()
return _effect.run.bind(_effect)
}
使用bind是因为this问题
结语
源码不止这些,这里只是做了一个极简的实现,包括TS类型,细节处理,测试环节,代码规范等,这些都需要后期处理,如果有哪些不对的还希望各位能够指正,也算是对我的洗礼了
后续也会有更多vue3源码及其他技术的文章