源码调试
yarn install 安装依赖
yarn dev 只打包了 packages下的 vue 文件,若要全部打包 执行 yarn build
reactvie.js
reactive、readonly、shallowReactive、shallowReadonly
import {mutableHandlers,readonlyHandlers,shallowReactiveHandlers,shallowReadonlyHandlers} from './baseHandlers'// 存储代理过的对象 WeakMap 会自动垃圾回收,不会造成内存泄漏,存储的key只能是对象export const reactiveMap = new WeakMap<Target, any>()export const shallowReactiveMap = new WeakMap<Target, any>()export const readonlyMap = new WeakMap<Target, any>()export const shallowReadonlyMap = new WeakMap<Target, any>()export function reactive(target: object) {// if trying to observe a readonly proxy, return the readonly version.if (target && (target as Target)[ReactiveFlags.IS_READONLY]) {return target}return createReactiveObject(target,false,mutableHandlers,mutableCollectionHandlers,reactiveMap)}export function shallowReactive<T extends object>(target: T): T {return createReactiveObject(target,false,shallowReactiveHandlers,shallowCollectionHandlers,shallowReactiveMap)}export function readonly<T extends object>(target: T): DeepReadonly<UnwrapNestedRefs<T>> {return createReactiveObject(target,true,readonlyHandlers,readonlyCollectionHandlers,readonlyMap)}export function shallowReadonly<T extends object>(target: T): Readonly<{ [K in keyof T]: UnwrapNestedRefs<T[K]> }> {return createReactiveObject(target,true,shallowReadonlyHandlers,shallowReadonlyCollectionHandlers,shallowReadonlyMap)}
这四个方法要根据是不是仅读,是不是深度来实现的核心功能 proxy 代理,所以就通过函数柯里化的方式,让 createReactiveObject 根据不同的传参实现最核心的数据拦截。
// 底层 new Proxy() 最核心的需要拦截 数据的读取和数据的修改function createReactiveObject(target: Target, // 响应式目标数据isReadonly: boolean, // 是不是仅读的baseHandlers: ProxyHandler<any>, // proxy 处理函数collectionHandlers: ProxyHandler<any>,proxyMap: WeakMap<Target, any> // 存储目标对象和代理后的对象映射关系) {// 如果目标不是对象就没法拦截 直接返回就行if (!isObject(target)) {if (__DEV__) {console.warn(`value cannot be made reactive: ${String(target)}`)}return target}// 如果这个对象被代理过了 就不要再代理了const existingProxy = proxyMap.get(target)if (existingProxy) {return existingProxy}// 最核心的 proxy 代理const proxy = new Proxy(target,baseHandlers)// 第一次代理将目标对象 和代理后的对象存起来proxyMap.set(target, proxy)return proxy}
首先判断目标是否对象,如果不是就不用代理了,直接返回。
其次,判断这个对象是否已经被代理过了,代理过就不用再代理了。
如果没有代理就通过 newProxy 实现代理,并返回代理后的对象。
最后将目标对象和代理后的对象存起来。
baseHandlers 是 代理处理函数,接下来我们就看看具体怎么根据不同的参数进行处理的。
这四个handler 都有 get 和 set 方法,所以抽离出来。
是不是仅读的,仅读的set时会报错
是不是深度代理
// TODO: 四种getterconst get = /*#__PURE__*/ createGetter()const shallowGet = /*#__PURE__*/ createGetter(false, true)const readonlyGet = /*#__PURE__*/ createGetter(true)const shallowReadonlyGet = /*#__PURE__*/ createGetter(true, true) // 仅第一层不能修改,第二次层可以修改
const set = /*#__PURE__*/ createSetter()const shallowSet = /*#__PURE__*/ createSetter(true)
下面是最核心的 get 和 set 的实现方法,触发 get 的时候要 收集依赖, 触发set 的时候要 更新依赖
// TODO: 拦截获取功能 是不是仅读的, 是不是 浅的 proxy + Reflect// Reflect 方法具备返回值设置成功过会返回 true ,失败返回 false// 会依赖收集,等数据变化了就更新对应的视图 trackfunction createGetter(isReadonly = false, shallow = false) {return function get(target: Target, key: string | symbol, receiver: object) { // target 原对象,key target 的属性, receiver 代理对象if (key === ReactiveFlags.IS_REACTIVE) {return !isReadonly} else if (key === ReactiveFlags.IS_READONLY) {return isReadonly} else if (key === ReactiveFlags.RAW &&receiver ===(isReadonly? shallow? shallowReadonlyMap: readonlyMap: shallow? shallowReactiveMap: reactiveMap).get(target)) {return target}// Reflect 取值 target[key]const res = Reflect.get(target, key, receiver)// 不是仅读就收集依赖if (!isReadonly) {// 仅读的属性不变,不用收集依赖,不是仅读的才会依赖收集track(target, TrackOpTypes.GET, key) // 监控哪个对象里的那个属性 TrackOpTypes.GET 操作标识,可能还有 HAS,表示对这个对象做什么操作的时候给他收集起来}// 浅代理 取出第一层 直接返回if (shallow) {return res}// 嵌套对象 readonly为true就用 readonly包裹 ,如果不是就递归用 reactive包裹// vue2 是一上来就递归,vue3 是当取值的时候会进行代理 懒代理if (isObject(res)) {return isReadonly ? readonly(res) : reactive(res)}return res}}
vue2 中在 取值的时候会触发依赖收集,vue3也是在get中进行依赖收集。
依赖收集的前提是这个这个api不是仅读的,仅读不会发生改变就不进行依赖收集。
不是仅读就会走 依赖收集。
如果是浅代理,就从target中取出第一层并直接返回,如果是嵌套对象就再判断他是否是readOnly,是的话就用 readOnly 包裹一层,不是的话就递归调用 reactive 包裹。
依赖收集

effect 是响应式包中的一个方法,默认页面刷新会执行一次,在执行这个 effect 方法的时候会 走get 方法获取响应式数据的属性值,所以可以将 effect 和 这个对象属性关联起来,当对象属性发生变化后会重新执行 effect 方法。这个属性跟vue2 的 update 方法很相似,但是不同的是 updata 任何属性发生变化都会重新渲染,而 effect 是只有依赖属性发生变化才会重新渲染。
// effect 变成响应式的effect,可以做到数据变化重新执行export function effect<T = any>(fn: () => T,options: ReactiveEffectOptions = EMPTY_OBJ): ReactiveEffect<T> {if (isEffect(fn)) {fn = fn.raw}// 例子:// effect( () => {// app.innerHTML = 'xxx',// })const effect = createReactiveEffect(fn, options)// 默认的 effect 会先执行一次 如果有lazy 就不执行if (!options.lazy) {effect() // 响应式的effect默认执行一次}return effect}// effect 标识,用于区分effect,effect 相当于 vue2 的 watcherlet uid = 0// 1.函数调用栈// effect( () => { effect1// state.name,// effect(()=>{ effect2// state.age,// })// state.address, effect2// })// 2.死循环// effect(() => {// state.age ++// })function createReactiveEffect<T = any>(fn: () => T, // fn 执行时会取值,会走 get 方法,就会依赖收集options: ReactiveEffectOptions): ReactiveEffect<T> {const effect = function reactiveEffect(): unknown {if (!effect.active) {return fn()}// 判断当前effect 是否在 栈中,避免死循环的情况if (!effectStack.includes(effect)) {cleanup(effect)try {enableTracking()effectStack.push(effect) // 执行这个effect的时候入栈activeEffect = effectreturn fn() // 函数执行完了} finally { // 用 finally 是希望函数执行异常了还能正确的将当前 effect pop 出去effectStack.pop() // 执行完了出战resetTracking()activeEffect = effectStack[effectStack.length - 1] // 当前的effect 永远指向栈中的最后一个effect}}} as ReactiveEffecteffect.id = uid++ // 每创建一个effect 就制作一个 effect 标识effect.allowRecurse = !!options.allowRecurseeffect._isEffect = true // 用于标识这个是响应式effecteffect.active = trueeffect.raw = fn // 响应式effect 对应的原函数effect.deps = []effect.options = options // 属性return effect}
effect 标识用于组件更新,更新哪个effect,
响应式effect
effect原函数
调用 effect 的时候会执行 effect 传递过来的参数 fn(),执行这个 fn 函数的时候会走 get 方法取值,这个时候就可以将 effect 和 依赖的属性关联起来,等 依赖的属性再次发生变化了就可以再次执行 effect 函数更新视图。
在上面的 createGetter 方法中有个 track 函数就是用来收集依赖的。一会儿看这块的源码。
我们可以看到有activeEffect和effectStack,activeEffect 用于标记当前执行的是哪个 effect 函数,那为什么要整一个函数调用栈?有一种情况如下:
effect( () => { effect1state.name,effect(()=>{ effect2state.age,})state.address, effect2})
当 effect2 执行完之后当前 activeEffect 还是等于 effect2,state.address 的取值就会变成 effect,这样就乱了。所以用一个函数调用栈,执行完就弹出,activeEffect 永远是 栈中的最后一个。
每次执行入栈,执行完出栈。
// 让对象中的某个属性收集他对应的 effect 函数// weakMap key => {name: 'hhh', age: 12}, value(是一个map,key:name,value:[effect]) =>{name: set[effect, effect]} 一个属性取几次就会存几个effect effect(()=>{state.name}) effect(()=>{state.name})export function track(target: object, type: TrackOpTypes, key: unknown) {// 只收集在 effect 使用的属性if (!shouldTrack || activeEffect === undefined) {return}// 判断对象是否有let depsMap = targetMap.get(target)if (!depsMap) { // 没有对象的话创建映射表targetMap.set(target, (depsMap = new Map()))}// 判断对象的key是否存在let dep = depsMap.get(key)if (!dep) { //对象的key没有的话创建映射表depsMap.set(key, (dep = new Set()))}// 对象的 key的 effect 是否在存在,不存在就放进去if (!dep.has(activeEffect)) {dep.add(activeEffect)activeEffect.deps.push(dep)if (__DEV__ && activeEffect.options.onTrack) {activeEffect.options.onTrack({effect: activeEffect,target,type,key})}}}
定义一个weakmap,key 就是响应式对象,value 可以也是一个map,这个map 的key 就是 响应式对象的属性,value就是他对应的 effect 函数,因为一个 属性可能对应好几个 effect 函数,所以 这个value 是一个 Set,set 的好处是可以去重。
{
 {name: ‘hhh’, age: 12}: {name:set[effect, effect]}
}
activeEffect 是当前执行的effect,vue3 和 vue2 都用一个全局变量标识当前effect。
只收集在effect 中用到的属性,没用到的就不收集。
用 set 存放 effect 的好处是可以去重,例如:
effect(()=>{state.name})effect(()=>{state.name})
这种情况 同一个属性就会收集两次effect。使用set 可以去重。
接下来就是当数据更新时通知对应属性的 effect 执行。
触发更新
vue2 无法监控更改数组索引,无法监控数组长度变化。
更新值 会走 set ,更新包括新增和修改。
对象区分新增属性和修改属性的方法就是看老值有没有,有就是 修改,没有就是新增。
数组区分新增和修改:
修改索引就看这个索引和length哪个大,如果索引大于length就是新增,如果索引小于length就是修改,判断索引的方法就是看这个 key 是否是字符串类型的数字。
// 判断是否是一个数字类型key
exportconstisIntegerKey = (key: unknown) =>
isString(key) &&
‘’ + parseInt(key, 10) === key
parseInt
parseInt转数字成整型,+ ‘’成字符串,如果不是字符串数字,parseInt 转完之后就是NaN,+ ‘’,之后和原来的不一样,就证明不是索引 。
新增 调用 trigger(), 触发类型 ADD,对应的key,value,修改触发类型是 SET,对应的key,新值,老值。
// TODO: 拦截设置功能 当数据更新时 通知对应属性的 effect 重新执行function createSetter(shallow = false) {// 区分新增的还是修改的 vue2 里无法监控更改索引,无法监控数组长度变化return function set(target: object,key: string | symbol,value: unknown,receiver: object): boolean {let oldValue = (target as any)[key] // 获取老值 如果老值存在就是修改,如果老值不存在就是新增if (!shallow) {value = toRaw(value)oldValue = toRaw(oldValue)if (!isArray(target) && isRef(oldValue) && !isRef(value)) { // 如果是数组oldValue.value = valuereturn 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)// don't trigger if target is something up in the prototype chain of originalif (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}}


触发更新的本质是让对应的 effect 函数执行,如果这个属性没有收集依赖,就不需要做任何事,直接返回即可。
找到这个属性对应的所有 effect 函数,存到一个新的集合中,然后一起触发更新。
修改数组的长度可能也会涉及到 索引的修改,如下:
effect(() =>{return state.arr[2]})setTimeout(() =>{state.arr.length = 1;})
// 触发更新export function trigger(target: object,type: TriggerOpTypes,key?: unknown,newValue?: unknown,oldValue?: unknown,oldTarget?: Map<unknown, unknown> | Set<unknown>) {const depsMap = targetMap.get(target)// 看看这个属性有没有被收集过 effect,没收集过就不需要做任何操作if (!depsMap) {// never been trackedreturn}// 将所有要执行的effect全部存在到一个新的集合中,最终一起执行const effects = new Set<ReactiveEffect>() // 这里对 effect 去重 effect(()=>{dom.innerHTML = state.arr[2] + state.arr.length}) 这种情况会收集一次effectconst add = (effectsToAdd: Set<ReactiveEffect> | undefined) => {if (effectsToAdd) {effectsToAdd.forEach(effect => {if (effect !== activeEffect || effect.allowRecurse) {effects.add(effect)}})}}// 这种情况会执行两次 effect// setTimeout(() => {// state.arr.length = 1;// state.arr.length = 3;// })if (type === TriggerOpTypes.CLEAR) {// collection being cleared// trigger all effects for targetdepsMap.forEach(add)} else if (key === 'length' && (target)) { // 1. 看修改的是不是数组长度 改长度影响比较大depsMap.forEach((dep, key) => {// 如果更改的长度小于收集的索引,那么这个索引也需要触发 effect 更新if (key === 'length' || key >= (newValue as number)) { // state.arr[2] state.arr.length = 1 2 >1add(dep)}})} else { // 改的不是数组了 可能是对象// schedule runs for SET | ADD | DELETEif (key !== void 0) { // 修改对象 新增不会走这儿add(depsMap.get(key))}// 执行收集的 effect// effects.forEach((effect) => effect())// also run for iteration key on ADD | DELETE | Map.SETswitch (type) {case TriggerOpTypes.ADD: // 如果修改的是数组的索引 state.arr[100] 索引大于数组的长度 那么就是新增 传过来的 type 就是addif (!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 changesadd(depsMap.get('length'))}breakcase TriggerOpTypes.DELETE:if (!isArray(target)) {add(depsMap.get(ITERATE_KEY))if (isMap(target)) {add(depsMap.get(MAP_KEY_ITERATE_KEY))}}breakcase 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()}}effects.forEach(run)}
流程图


