序言
vue3对整个响应式都做了一次重大升级,有原来的defineProperty,改为全新的Proxy进行重构。
先看看defineProperty的局限性
- 对数组的操作有局限性,仅对Array下的方法进行hack处理
- 基于属性拦截,初始化时会递归全部的属性,对性能稍有影响,且对未有的属性监听不到
而Proxy的出现恰恰弥补了上面的缺陷。
源码结构
├─packages
│ ├─reactivity
│ ├─src
│ ├─baseHandlers.ts
│ ├─collectionHandlers.ts
│ ├─computed.ts
│ ├─effect.ts
│ ├─operations.ts
│ ├─reactive.ts
│ ├─ref.ts
目标源对象
vue3的响应式flag,用于标记目标target对象,下面是枚举的属性:
export const enum ReactiveFlags {
SKIP = '__v_skip',
IS_REACTIVE = '__v_isReactive',
IS_READONLY = '__v_isReadonly',
RAW = '__v_raw'
}
export interface Target {
[ReactiveFlags.SKIP]?: boolean // 跳过,不对target做响应式处理
[ReactiveFlags.IS_REACTIVE]?: boolean // target是响应式的
[ReactiveFlags.IS_READONLY]?: boolean // target是只读的
[ReactiveFlags.RAW]?: any // target已经是Proxy对象时会有该属性,表示Proxy对应的源数据
}
😀 我们来一一解析它。
baseHandlers.ts
该文件主要是组合Proxy的基础配置参数:
有两个重要的方法 createGetter 和 createSetter。
createGetter
// createGetter
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 访问对应标志位的处理逻辑
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
}
else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
}
else if (
// receiver指向调用者,这里的判断是为了保证触发拦截handler的是proxy对象本身而非proxy的继承者。
// 触发拦截器的两种途径:
// 1 访问proxy对象本身的属性;
// 2 访问对象原型链上有proxy对象的对象的属性,因为查询属性会沿着原型链向下游依次查询,因此同样会触发拦截器
key === ReactiveFlags.RAW && receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
// 返回target本身,即响应式对象的原始值
return target
}
// 访问Array对象上的方法,存储在 arrayInstrumentations 工具集里
const targetIsArray = isArray(target)
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
// 这里在调用indexOf等数组方法时是通过proxy来调用的,因此
// arrayInstrumentations[key]的this一定指向proxy实例
// 也即receiver
return Reflect.get(arrayInstrumentations, key, receiver)
}
// Proxy预返回值
const res = Reflect.get(target, key, receiver)
// key是symbol或访问的是__proto__属性不做依赖收集和递归响应式转化,直接返回结果
if (
isSymbol(key)
? builtInSymbols.has(key as symbol)
: key === `__proto__` || key === `__v_isRef`
) {
return res
}
// 不是只读target才需收集依赖,
// 只读的因为属性不会变化,因此无法触发setter,也就不会触发依赖更新
if (!isReadonly) {
// 通过track函数将依赖存储到对应的全局仓库中
track(target, TrackOpTypes.GET, key)
}
// 浅比较不做递归转化
if (shallow) {
return res
}
// 访问属性已经是ref对象,保证访问ref属性时得到的是ref对象的value属性值,数组除外
if (isRef(res)) {
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 如果子元素是对象,需要递归进行响应式转化
if (isObject(res)) {
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
createSetter
// createSetter
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
const oldValue = (target as any)[key]
if (!shallow) {
// 通过setter入参的新值获取它的原始值,新传入值可能是响应式数据,如果直接和
// target上的原始值比较是没有任何实际意义的
value = toRaw(value)
// target不是数组,且旧值为ref,新值非ref,直接将ref.value更新为新值
if (!isArray(target) && isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
}
} else {
// in shallow mode, objects are set as-is regardless of reactive or not
}
const hadKey =
isArray(target) && isIntegerKey(key)
? Number(key) < target.length
: hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// 这里判断receiver是proxy实例才更新派发,防止通过原型链触发拦截器触发更新。
if (target === toRaw(receiver)) {
if (!hadKey) {
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
用法:
const get = /*#__PURE__*/ createGetter()
const set = /*#__PURE__*/ createSetter()
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
这里的mutableHandlers就会暴露给createReactiveObject那边。
arrayInstrumentations
vue3单独处理数组的工具集:
const arrayInstrumentations: Record<string, Function> = {}
// 获取索引的方法
(['includes', 'indexOf', 'lastIndexOf'] as const).forEach(key => {
const method = Array.prototype[key] as any
arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
// 去掉响应式wrapper,获取源数组对象
const arr = toRaw(this)
// 按照数组下标收集依赖
for (let i = 0, l = this.length; i < l; i++) {
track(arr, TrackOpTypes.GET, i + '')
}
// 优先调用Array原生方法查找传入值
const res = method.apply(arr, args)
if (res === -1 || res === false) {
// 没有查询到对应的值,有可能是包装后的响应式数据,有wrapper,引用不同所以
// 有可能查不到,尝试去掉wrapper再查询
return method.apply(arr, args.map(toRaw))
} else {
return res
}
}
})
// 改变数组长度的方法
(['push', 'pop', 'shift', 'unshift', 'splice'] as const).forEach(key => {
const method = Array.prototype[key] as any
arrayInstrumentations[key] = function(this: unknown[], ...args: unknown[]) {
// 执行前禁用依赖收集,
/**
* 这里主要是为了避免改变数组长度时,会set length,形成track - trigger的死循环
* 因此要暂停改变数组长度时的执行期间收集依赖
*/
pauseTracking()
const res = method.apply(this, args)
// 执行后恢复之前track状态
resetTracking()
return res
}
})
effect.ts
该文件主要是处理副作用,主要是以下内容:
- 创建 effect
- track 依赖收集器
- trigger 派发
- clearup 清除
- stop 停止effect
- trackStack 收集栈的暂停、恢复、重置处理
createReactiveEffect
创建effect函数:
参数fn是执行函数,会触发target[key]的getter,完成依赖收集。
在依赖收集前后采用栈数据结构(effectStack)来做effect的执行调度,保证当前effect的优先级最高,并及时清除已收集依赖的内存。
const effectStack: ReactiveEffect[] = []
let activeEffect: ReactiveEffect | undefined
function createReactiveEffect<T = any>(
fn: () => T,
options: ReactiveEffectOptions
): ReactiveEffect<T> {
const effect = function reactiveEffect(): unknown {
if (!effect.active) {
return options.scheduler ? undefined : fn()
}
// effectStack是一个全局effect栈,用于effect调度。
if (!effectStack.includes(effect)) {
// 先清除,防止effect重复添加
cleanup(effect)
try {
// 在setup函数自行期间,暂停依赖收集,因此这里需要恢复
enableTracking()
// effect入栈
effectStack.push(effect)
// 将当前effect设置为全局激活effect,在getter中会收集activeEffect持有的effect
// activeEffect表示当前依赖收集系统正在处理的effect
activeEffect = effect
// 执行effect的执行函数,比如访问target[key],会触发getter
return fn()
} finally {
// 此时fn执行完毕,依赖已收集到依赖仓库,因此将当前effect出栈,及时释放内存
effectStack.pop()
resetTracking()
// 将activeEffect恢复到effect栈中最后一个effect,即上次的activeEffect,继续
// 做该effect的收集工作
activeEffect = effectStack[effectStack.length - 1]
}
}
} as ReactiveEffect
effect.id = uid++ // effect的uid
effect.allowRecurse = !!options.allowRecurse
effect._isEffect = true // 标记是否是effect
effect.active = true // effect睡否处于激活状态
effect.raw = fn // effect的执行函数
effect.deps = [] // 当前effect的dep数组
effect.options = options // effect的配置项,包含配置项及hooks
return effect
}
track
依赖收集器:
这里看思维导图 https://www.yuque.com/qqhh/gb15wo/rl55vs
const targetMap = new WeakMap<any, KeyToDepMap>()
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 如果当前没有激活的effect,就不用收集
// shouldTrack===false 时,暂停收集,详情见下面的trackStack
if (!shouldTrack || activeEffect === undefined) {
return
}
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(activeEffect)) {
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
}
trigger
派发:trigger是track收集相对应的触发器。
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
const depsMap = targetMap.get(target)
if (!depsMap) {
// 没有收集
return
}
// 普通effect集合
const effects = new Set<ReactiveEffect>()
// effect的集合函数
// add函数可以过滤出符合条件的依赖,然后批量触发
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
/**
这里判断effect !== activeEffect的原因:
不能和当前的effect相同,是因为有一种情况有问题: count.value++
如果这是个effect,它会触发 getter,track收集了当前激活的effect,
然后 count.value = count.value+1 会触发 setter,并且执行trigger,
会陷入一个死循环,所以要过滤当前的effect。
*/
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
if (type === TriggerOpTypes.CLEAR) {
// 清空依赖
depsMap.forEach(add)
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
// 源对象是数组且trigger的key是length,需要触发length对应的effects,
// 同时,由于触发length时一定是数组长度变化了,当数组长度变短时,需要
// 做已删除数组元素的effects的trigger工作,也就是索引号 >= 数组
// 最新length的元素们所对应的effects,对它们做清除后的批量触发。
add(dep)
}
})
} else {
// key不是undefined就会触发依赖局部添加,只不过如果是新增属性对应的dep为空
if (key !== void 0) {
add(depsMap.get(key))
}
// 新增属性或者删除属性的处理
switch (type) {
case TriggerOpTypes.ADD:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
} else if (isIntegerKey(key)) {
add(depsMap.get('length'))
}
break
case TriggerOpTypes.DELETE:
if (!isArray(target)) {
add(depsMap.get(ITERATE_KEY))
if (isMap(target)) {
add(depsMap.get(MAP_KEY_ITERATE_KEY))
}
}
break
case TriggerOpTypes.SET:
if (isMap(target)) {
add(depsMap.get(ITERATE_KEY))
}
break
}
}
// run函数用来批量执行add方法添加过的effect
const run = (effect: ReactiveEffect) => {
if (__DEV__ && effect.options.onTrigger) {
effect.options.onTrigger({
effect,
target,
key,
type,
newValue,
oldValue,
oldTarget
})
}
if (effect.options.scheduler) {
effect.options.scheduler(effect)
} else {
effect()
}
}
effects.forEach(run)
}
clearup
清除一个effect:
function cleanup(effect: ReactiveEffect) {
const { deps } = effect // effect上面的收集数组
if (deps.length) {
for (let i = 0; i < deps.length; i++) {
deps[i].delete(effect)
}
deps.length = 0
}
}
effect
暴露一个创建effect的方法:
export function effect<T = any>(
fn: () => T,
options: ReactiveEffectOptions = EMPTY_OBJ
): ReactiveEffect<T> {
if (isEffect(fn)) {
fn = fn.raw
}
const effect = createReactiveEffect(fn, options)
// options.lazy 默认是false,自执行一次effect
if (!options.lazy) {
effect()
}
return effect
}
stop
暴露一个停止(清除)effect的方法:
export function stop(effect: ReactiveEffect) {
if (effect.active) {
cleanup(effect)
if (effect.options.onStop) {
effect.options.onStop()
}
effect.active = false
}
}
trackStack
收集栈的一些方法:暂停如栈、恢复入栈、恢复到最后一次栈
let shouldTrack = true
const trackStack: boolean[] = []
export function pauseTracking() {
trackStack.push(shouldTrack)
shouldTrack = false
}
export function enableTracking() {
trackStack.push(shouldTrack)
shouldTrack = true
}
export function resetTracking() {
const last = trackStack.pop()
shouldTrack = last === undefined ? true : last
}
reactive.ts_
该文件是响应式的核心,主要有以下内容:
- createReactiveObject 创建响应式对象
- shallowReactive 创建浅层(第一层)的响应式,不深度递归
- readonly 创建只读响应式代理
- 判断是否响应式对象
- 判断是否是只读的对象
- toRaw 获取响应式源对象
- markRaw 创建一个永远不会被响应式代理的对象
createReactiveObject
createReactiveObject 将target转换为响应式对象。
// 将target转化为响应式对象
function createReactiveObject(
target, // 源对象
isReadonly, // 是否只读
baseHandlers, // proxy的handlers,来源baseHandlers.ts
collectionHandlers // 来源collectionHandlers.ts
) {
// target只能是object
if (!isObject(target)) {
{
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// __v_raw仅当数据源已经是Proxy时才会有
// 判断如果target已经是响应式了直接返回
if (
target['__v_raw' /* RAW */] &&
!(isReadonly && target['__v_isReactive' /* IS_REACTIVE */])
) {
return target
}
// 判断是否是只读
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
// 如果该target已经在map中了
if (existingProxy) {
return existingProxy
}
// 不在响应式的白名单,直接返回
const targetType = getTargetType(target)
if (targetType === 0 /* INVALID */) {
return target
}
// 将target转换为proxy
const proxy = new Proxy(
target,
targetType === 2 /* COLLECTION */ ? collectionHandlers : baseHandlers
)
proxyMap.set(target, proxy)
return proxy
}
isReactive
判断是否是一个响应式对象;
export function isReactive(value: unknown): boolean {
if (isReadonly(value)) {
// 如果是只读的,取target源对象
return isReactive((value as Target)[ReactiveFlags.RAW])
}
return !!(value && (value as Target)[ReactiveFlags.IS_REACTIVE])
}
isReadonly
判断是否是一个只读对象;
export function isReadonly(value: unknown): boolean {
return !!(value && (value as Target)[ReactiveFlags.IS_READONLY])
}
isProxy
判断是否是响应式对象或只读对象 (他们都是proxy对象);
export function isProxy(value: unknown): boolean {
return isReactive(value) || isReadonly(value)
}
toRaw
获取响应式源对象
export function toRaw<T>(observed: T): T {
return (
// ReactiveFlags.RAW 就是上面的指向目标源对象的key
(observed && toRaw((observed as Target)[ReactiveFlags.RAW])) || observed
)
}
computed.ts
computed
class ComputedRefImpl {
constructor(getter, _setter, isReadonly) {
this._setter = _setter
this._dirty = true
this.__v_isRef = true
this.effect = effect(getter, {
lazy: true, // 初始化effect时,默认不执行getter
scheduler: () => {
// getter的时候,不派发通知
if (!this._dirty) {
this._dirty = true
trigger(toRaw(this), 'set' /* SET */, 'value')
}
},
})
this['__v_isReadonly' /* IS_READONLY */] = isReadonly
}
get value() {
if (this._dirty) {
// 赋值
this._value = this.effect()
this._dirty = false
}
// compunted 收集依赖
track(toRaw(this), 'get' /* GET */, 'value')
return this._value
}
set value(newValue) {
this._setter(newValue)
}
}
function computed(getterOrOptions) {
let getter
let setter
if (isFunction(getterOrOptions)) {
getter = getterOrOptions
setter = () => {
console.warn('Write operation failed: computed value is readonly')
}
} else {
getter = getterOrOptions.get
setter = getterOrOptions.set
}
return new ComputedRefImpl(
getter,
setter,
isFunction(getterOrOptions) || !getterOrOptions.set
)
}
看到 computed 的参数,我们发现有两种参数:
- getterOrOptions 是函数,会生成effect进行依赖收集;
- getterOrOptions 是对象,有 get和set属性,可实现自定义配置;
ref.ts
该文件主要是处理vue中ref的;
RefImpl
ref的实现类;
class RefImpl<T> {
private _value: T
// 没有ref实例下都有一个 __v_isRef 的只读属性,标识它是一个ref
public readonly __v_isRef = true
constructor(private _rawValue: T, public readonly _shallow = false) {
// 如果不是浅比较,convert会判断如果是object,则调用 reactive 方法 (createReactiveObject)
this._value = _shallow ? _rawValue : convert(_rawValue)
}
get value() {
// ref.value,我们要这样取值,
// 依赖收集器执行
track(toRaw(this), TrackOpTypes.GET, 'value')
return this._value
}
set value(newVal) {
// 比较新值和旧值
if (hasChanged(toRaw(newVal), this._rawValue)) {
this._rawValue = newVal
// 值已更换,重新赋值
// 派发通知
this._value = this._shallow ? newVal : convert(newVal)
trigger(toRaw(this), TriggerOpTypes.SET, 'value', newVal)
}
}
}
createRef
创建一个ref对象;
function isRef(r) {
return Boolean(r && r.__v_isRef === true)
}
// 创建一个ref对象
function createRef(rawValue: unknown, shallow = false) {
// 判断是否已经是一个ref对象
if (isRef(rawValue)) {
return rawValue
}
return new RefImpl(rawValue, shallow)
}
// 创建一个浅比较的ref
function shallowRef(value) {
return createRef(value, true)
}
unref
卸载一个ref对象;
function unref(ref) {
return isRef(ref) ? ref.value : ref
}
proxyRefs
创建一个Proxy对象的ref;
const shallowUnwrapHandlers: ProxyHandler<any> = {
get: (target, key, receiver) => unref(Reflect.get(target, key, receiver)),
set: (target, key, value, receiver) => {
const oldValue = target[key]
if (isRef(oldValue) && !isRef(value)) {
oldValue.value = value
return true
} else {
return Reflect.set(target, key, value, receiver)
}
}
}
export function proxyRefs<T extends object>(
objectWithRefs: T
): ShallowUnwrapRef<T> {
// 如果不是一个响应式对象,重新 new Proxy
return isReactive(objectWithRefs)
? objectWithRefs
: new Proxy(objectWithRefs, shallowUnwrapHandlers)
}
customRef
自定义ref,可以对其依赖项跟踪和派发进行显式控制。
class CustomRefImpl<T> {
private readonly _get: ReturnType<CustomRefFactory<T>>['get']
private readonly _set: ReturnType<CustomRefFactory<T>>['set']
public readonly __v_isRef = true
constructor(factory: CustomRefFactory<T>) {
const { get, set } = factory(
() => track(this, TrackOpTypes.GET, 'value'),
() => trigger(this, TriggerOpTypes.SET, 'value')
)
this._get = get
this._set = set
}
get value() {
return this._get()
}
set value(newVal) {
this._set(newVal)
}
}
export function customRef<T>(factory: CustomRefFactory<T>): Ref<T> {
return new CustomRefImpl(factory) as any
}
实例用法:
<template>
<input v-model="handleCustome">
</template>
<script>
import { customRef } from vue;
export default {
setup(){
const handleCustome = (value, delay = 200) => {
let timer;
// 这里的参数是工厂函数,接收2个参数一个track,一个trigger
return customRef((track, trigger) => {
return {
get(){
track();
return value;
},
set(newValue){
clearTimeout(timer);
timer = setTimeout(()=>{
value = newValue;
trigger();
}, delay)
}
}
})
}
return {
handleCustome: handleCustome('')
}
}
}
</script>
toRefs
可以将数据转换为ref对象。
function toRefs(object) {
if (!isProxy(object)) {
console.warn(
`toRefs() expects a reactive object but received a plain one.`
)
}
const ret = isArray(object) ? new Array(object.length) : {}
for (const key in object) {
ret[key] = toRef(object, key)
}
return ret
}
class ObjectRefImpl {
constructor(_object, _key) {
this._object = _object
this._key = _key
this.__v_isRef = true
}
get value() {
return this._object[this._key]
}
set value(newVal) {
this._object[this._key] = newVal
}
}
function toRef(object, key) {
return isRef(object[key]) ? object[key] : new ObjectRefImpl(object, key)
}
实例用法:
<template>
<div>
{{name}}
</div>
</template>
<script>
import { reactive, toRefs } from vue;
export default {
setup(){
const foo = reactive({ name: 'Hello World' })
return {
...toRefs(foo)
}
/**
这里如果不用 toRefs的话,就只能这样用
return { foo }
在模版里面这样用
{{foo.name}}
如果直接 return { ...foo } 的话,reactive的响应式就会失效,
因为reactive实例下有很多属性,解构后就丢失了,所以就没有响应式数据了。
*/
}
}
</script>
完!
👏 恭喜你,看到这里,有没有发现自己离大神又近了一步!!! 😜😜😜