概述
响应式原理主要由 3 个部分构成:1. 初始化;2. 收集依赖;3. 触发依赖
Vue3.0 观测数据提供4个方法:reactive, shallowReactive, readonly, shallowReadonly
下面从 reactive 入手分析响应式原理(Vue 3.0.5 版本)
Demo
<div id="demo">
<!-- 2. 收集依赖 -->
<h1 @click="handleAddCount"> Vue.js Count: {{state.count}}</h1>
</div>
<script>
import { reactive } from 'vue'
export default{
setup() {
// 1. 初始化
const state = reactive({ count: 1 })
const handleAddCount = () => {
// 3. 触发依赖
state.count ++
}
return {
state,
handleAddCount
}
}
}
</script>
1. 初始化阶段
调用代码
const state = reactive({count: 1}) // 传入 Object,返回 Proxy
源码位置: reactive.ts
export function reactive(target: object) {
// 如果观测一个 readonly proxy 直接返回
if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {
return target
}
return createReactiveObject(
target,
false,
mutableHandlers,
mutableCollectionHandlers
)
}
function createReactiveObject(
target: Target,
isReadonly: boolean,
baseHandlers: ProxyHandler<any>,
collectionHandlers: ProxyHandler<any>
) {
// 1. 非对象不允许观测
if (!isObject(target)) {
if (__DEV__) {
console.warn(`value cannot be made reactive: ${String(target)}`)
}
return target
}
// 2. target 已经是一个 Proxy,直接返回
// target is already a Proxy, return it.
// exception: calling readonly() on a reactive object
if (
target[ReactiveFlags.RAW] &&
!(isReadonly && target[ReactiveFlags.IS_REACTIVE])
) {
return target
}
// 3.target 已经是一个观测过的对象,直接返回
// 但是一个对象可以经 reactive, readonly 都观测一次
const proxyMap = isReadonly ? readonlyMap : reactiveMap
const existingProxy = proxyMap.get(target)
if (existingProxy) {
return existingProxy
}
// 4. 仅白名单类型的对象可以观测
const targetType = getTargetType(target)
// targetType === TargetType.INVALID 有如下情况
// a. 经 markRaw 处理的对象(__v_skip属性为true)
// b. 禁止扩展的对象(Object.preventExtensions, Object.seal, Object.freeze 处理过)
// c. 非 Object, Array, Map, Set, WeakMap, WeakSet 的对象(比如 Date 对象)
if (targetType === TargetType.INVALID) {
return target
}
// 5. 进行观测,普通对象(Object, Array)和 COLLECTION 对象(Map, Set, WeakMap, WeakSet)会有不同的处理,下面着重分析普通对象
const proxy = new Proxy(
target,
targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
)
// 6. 保存 target 到 Map
proxyMap.set(target, proxy)
return proxy
}
2. 收集依赖阶段
调用代码
// 渲染节点时等同于执行下面的代码,renderTemplate 方法模拟渲染 dom 的 render 方法
// 会对 state.count 进行取值,触发了 state 的 get 拦截
watchEffect(() => {
renderTemplate(
`<h1 @click="handleAddCount"> Vue.js Count: {{state.count}}</h1>`,
{ state, handleAddCount }
)
})
源码位置: baseHandlers.ts & effect.ts
// baseHandlers.ts
const get = /*#__PURE__*/ createGetter()
function createGetter(isReadonly = false, shallow = false) {
return function get(target: Target, key: string | symbol, receiver: object) {
// 1. 处理标识属性的值
// 1.1 ReactiveFlags.IS_REACTIVE => __v_isReactive 是否是响应式
if (key === ReactiveFlags.IS_REACTIVE) {
return !isReadonly
// 1.2 ReactiveFlags.IS_READONLY => __v_isReadonly 是否是只读
} else if (key === ReactiveFlags.IS_READONLY) {
return isReadonly
// 1.3 ReactiveFlags.RAW => __v_raw 原始对象
} else if (
key === ReactiveFlags.RAW &&
receiver === (isReadonly ? readonlyMap : reactiveMap).get(target)
) {
return target
}
const targetIsArray = isArray(target)
// 2. 劫持数组方法(proxy 不能拦截的操作,vue 将进行如下 hack 处理)
// 2.1 'includes', 'indexOf', 'lastIndexOf' 执行这 3 个方法时遍历收集数组所有元素的依赖
// 2.2 'push', 'pop', 'shift', 'unshift', 'splice' 获取这些方法时会暂时他停止收集依赖
if (!isReadonly && targetIsArray && hasOwn(arrayInstrumentations, key)) {
return Reflect.get(arrayInstrumentations, key, receiver)
}
const res = Reflect.get(target, key, receiver)
// 3. 返回 key 为 Symbol 类型的数据,不会收集依赖
if (
isSymbol(key)
? builtInSymbols.has(key as symbol)
: key === `__proto__` || key === `__v_isRef`
) {
return res
}
// 4. 收集依赖(只读属性不会触发依赖收集,因为永远不会触发 set,也就不用收集依赖)
if (!isReadonly) {
// track 方法详解见后面
track(target, TrackOpTypes.GET, key)
}
// 5. 浅观测只收集第一层属性的依赖
if (shallow) {
return res
}
// 6. 对于属性值为 ref 时,对象会自动解包,数组则不会
if (isRef(res)) {
// ref unwrapping - does not apply for Array + integer key.
const shouldUnwrap = !targetIsArray || !isIntegerKey(key)
return shouldUnwrap ? res.value : res
}
// 7. 对于深层的对象懒观测,即只有在 get 该值时才会进行观测,有利于提升性能
if (isObject(res)) {
// Convert returned value into a proxy as well. we do the isObject check
// here to avoid invalid value warning. Also need to lazy access readonly
// and reactive here to avoid circular dependency.
return isReadonly ? readonly(res) : reactive(res)
}
return res
}
}
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
// effect.ts
// targetMap 作为全局依赖,所有被观测的对象的依赖全部保存在这
// 观测粒度为对象的 key,数据结构如下
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()
// 2. 依赖收集方法
export function track(target: object, type: TrackOpTypes, key: unknown) {
// 1. 判断是否允许收集
// shouldTrack:用于标识是否可执行 track,由内部方法 pauseTracking和 enableTracking 切换状态
// activeEffect:表示当前执行的 effect,即要收集的依赖(本案例中当 watchEffect 传入的函数对应的 effect 运行时就会被标记为 activeEffect)
if (!shouldTrack || activeEffect === undefined) {
return
}
// 2. 获取 target 的所有依赖,如果不存在则进行初始化
let depsMap = targetMap.get(target)
if (!depsMap) {
targetMap.set(target, (depsMap = new Map()))
}
// 3. 获取 target 对应 key 的所有依赖,如果不存在则进行初始化
let dep = depsMap.get(key)
if (dep === void 0) {
depsMap.set(key, (dep = new Set()))
}
// 4. 如果当前依赖不存在则保存
if (!dep.has(activeEffect)) {
// 加入依赖
dep.add(activeEffect)
activeEffect.deps.push(dep)
if (__DEV__ && activeEffect.options.onTrack) {
activeEffect.options.onTrack({
effect: activeEffect,
target,
type,
key
})
}
}
// 依赖收集完毕
}
3. 触发依赖阶段
调用代码
const handleAddCount = () => {
state.count ++ // 此时触发
}
源码位置: baseHandlers.ts & effect.ts
// baseHandlers.ts
const set = /*#__PURE__*/ createSetter()
// 1. 设置 state.count 触发的 set 拦截
function createSetter(shallow = false) {
return function set(
target: object,
key: string | symbol,
value: unknown,
receiver: object
): boolean {
// 1. 获取对象 key 对应的旧值
const oldValue = (target as any)[key]
if (!shallow) {
value = toRaw(value)
// 2. 自动解包 ref 对象,设置 value
// 此时 oldValue.value 又会触发 ref 对象的 set,从而触发其依赖触发
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 = hasOwn(target, key)
const result = Reflect.set(target, key, value, receiver)
// don't trigger if target is something up in the prototype chain of original
if (target === toRaw(receiver)) {
// 3. 判断当前 key 是否已存在,分别触发 add 和 set 类型的 trigger
if (!hadKey) {
// 新增属性也会触发依赖更新,不像 vue2.0 中必须使用 $set
trigger(target, TriggerOpTypes.ADD, key, value)
} else if (hasChanged(value, oldValue)) {
// 当值有改变时才会触发依赖
trigger(target, TriggerOpTypes.SET, key, value, oldValue)
}
}
return result
}
}
export const mutableHandlers: ProxyHandler<object> = {
get,
set,
deleteProperty,
has,
ownKeys
}
// effect.ts
// 触发依赖的执行
export function trigger(
target: object,
type: TriggerOpTypes,
key?: unknown,
newValue?: unknown,
oldValue?: unknown,
oldTarget?: Map<unknown, unknown> | Set<unknown>
) {
// 1. 获取 target 对应的依赖
const depsMap = targetMap.get(target)
if (!depsMap) {
// never been tracked
return
}
// 2. 获取要执行的 effect 队列
const effects = new Set<ReactiveEffect>()
const add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {
if (effectsToAdd) {
effectsToAdd.forEach(effect => {
if (effect !== activeEffect || effect.allowRecurse) {
effects.add(effect)
}
})
}
}
// 3. collection 执行 clear 时,将所有 key 的依赖都添加到队列
if (type === TriggerOpTypes.CLEAR) {
// collection being cleared
// trigger all effects for target
depsMap.forEach(add)
// 4. 当修改数组的 length 时,将数组 length 和 大于数组 length 下标的依赖都添加到队列
} else if (key === 'length' && isArray(target)) {
depsMap.forEach((dep, key) => {
if (key === 'length' || key >= (newValue as number)) {
add(dep)
}
})
// 5. 处理常见的 修改,新增,删除 操作
} else {
// 5.1 找到 key 对应的依赖添加到队列
// schedule runs for SET | ADD | DELETE
if (key !== void 0) {
add(depsMap.get(key))
}
// 5.2 找到 ITERATE_KEY 的依赖添加到队列(在 ownKeys 拦截中收集)
// also run for iteration key on ADD | DELETE | Map.SET
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)) {
// new index added to array -> length changes
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
}
}
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()
}
}
// 6. 执行队列中所有依赖
effects.forEach(run)
}
关于对 Map, Set, WeakMap, WeakSet 的观测
源码位置: collectionHandlers.ts
概述
均只拦截了 get 方法,然后通过代理对象 重写 colection 的 get, set, has, add, set, delete, clear, forEach 来实现依赖收集和触发,原理和对象的拦截类型,有兴趣的同学可以自行了解
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!