搭错车的小火柴

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: 订阅 订阅 :: 管理 ::

看源码时候做的笔记-----------

 1.ref是什么?

from:vue-next/packages/reactivity/src/ref.ts

示例:

import { ref, ref } from 'vue';
let a: Ref<String> = ref('hello world');
let b: Ref<Object> = ref({name: 'hello world'});
console.log(b)

生成的变量:

 首先变量a的类型是 Ref<String>,这个类型 Ref的定义如下,包含了 value,_shallow,[RefSymbol]。我们从上图看,ref变量里面还有 __v_isRef、_rawValue、_value、_shallow

declare const RefSymbol: unique symbol

export interface Ref<T = any> {
  value: T
  /**
   * Type differentiator only.
   * We need this to be in public d.ts but don't want it to show up in IDE
   * autocomplete, so we use a private Symbol instead.
   */
  [RefSymbol]: true
  /**
   * @internal
   */
  _shallow?: boolean
}

再看看 ref 的实现:

(ts的函数重载:  https://jkchao.github.io/typescript-book-chinese/typings/functions.html#%E9%87%8D%E8%BD%BD

export function ref<T extends object>(value: T): ToRef<T> 
export function ref<T>(value: T): Ref<UnwrapRef<T>>
export function ref<T = any>(): Ref<T | undefined>
export function ref(value?: unknown) {
  return createRef(value)
}

function createRef(rawValue: unknown, shallow = false) {
  if (isRef(rawValue)) {
    return rawValue
  }
 //
RefImpl 实例会初始化 __v_isRef、_rawValue、_value、_shallow
//  __v_isRef 用在isRef func中判断,当前变量是不是ref
return new RefImpl(rawValue, shallow)
}
RefImpl在构造ref的时候 ,会判断用户传入的_rawValue是不是object,不是object就直接赋值给_value, 是object就 convert(_rawValue) 
const convert = <T extends unknown>(val: T): T =>
  isObject(val) ? reactive(val) : val

可以看出来,最后将object类型的变量,构造为reactive的proxy。

 

 2.reactive是什么?

 from:vue-next/packages/reactivity/src/reactive.ts

响应性基础API:https://www.vue3js.cn/docs/zh/api/basic-reactivity.html#reactive

2.1.reactiveMap 和 readonlyMap 为什么是 weakMap,WeakMap 和 Map有什么区别?

Map 是由 key 数组和 value 数组构成,遍历时,先遍历 key, 找到 index , 然后再从 value 数组取值。两个很大的缺点:

  a) 赋值和搜索操作都是 O(n) 的时间复杂度( n 是键值对的个数),因为这两个操作都需要遍历全部整个数组来进行匹配。

  b) 是可能会导致内存泄漏,因为数组会一直引用着每个键和值。这种引用使得垃圾回收算法不能回收处理他们,即使没有其他任何引用存在了。

WeakMap 对象是一组键/值对的集合,其中的键是弱引用的,因为是弱引用,所以key不可被枚举,如果key 是可枚举的话,其列表将会受垃圾回收机制的影响,从而得到不确定的结果。其键必须是对象,而值可以是任意的。

  a) WeakMap 持有的是每个键对象的“弱引用”,而弱引用的对象,垃圾回收机制不考虑对该对象的引用,这意味着在没有其他引用存在时垃圾回收能正确进行。

  原生 WeakMap 的结构是特殊且有效的,其用于映射的 key 只有在其没有被回收时才是有效的。

effec.ts 中,Dep是个set,KeyToDepMap 是个 Map,targetMap 也是个WeakMap。

2.2.Reflect 和 Object 有什么区别?

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Reflect/Comparing_Reflect_and_Object_methods

2.3 怎么定义reactive数据?

可以创建reactive、shallowReactive、readonly、shallowReadonly四种不同的reactive数据类型,具体解释见:https://www.vue3js.cn/docs/zh/api/basic-reactivity.html#markraw

每种reactive类型对应不同的createHandler,分别是mutableHandlers、shallowReactiveHandlers、readonlyHandlers、shallowReadonlyHandlers,

 

 创建过程中,需要检测teaget是否是readonly、是否已经是reactive、是否已经被标记为raw类型、是否是基础类型不可扩展、是否已经存在于map中,并做相应处理。

未创建并且满足创建条件的,将target转换为proxy:

baseHandlers 和 collectionHandlers,负责给proxy传入不同的劫持handler。

2.4 怎么创建reactive数据类型?

 那 collectionHandlers(适用于Map/WeakMap/Set/WeakSet类型的target)和  baseHandlers (适用于Object和Array类型的target)有什么区别?

2.4.1:baseHandlers:vue-next/packages/reactivity/src/baseHandlers.ts

其实baseHandler主要是为proxy提供了handler:

reactiveVar 
    = new Proxy(
        {},
        {
            get,
            set,
            has,
            deleteProperty,
            ownKeys
        }
    )
    = new Proxy({},handler)

 在baseHandlers中,createGetter 负责创建get/shallowGet/readonlyGet/shallowReadonlyGet,区分在key是symbol、readonly、shallow、ref等情况下key的返回值,根绝情况确认是否要track追踪收集依赖。

createGetter(isReadonly = false, shallow = false) {
  // 处理 __v_isReactive、__v_isReadonly、__v_raw 情况
  // 处理 数组取值
  // 处理 key 是symbol的情况
  // track
  // 处理shallow和ref的情况
  // 处理 res(Reflect.get(target, key, receiver))是object的情况,递归处理nested varabiles
}

createSetter 负责创建 set/shallowSet,(readonly不需要set)

function set( target: object, key: string | symbol, value: unknown, receiver: object): boolean {
    // 处理 非shallow非数组且isRef的情况,直接赋值
    // 处理 数组复制的情况 Reflect.set(target, key, value, receiver), trigger
}
2.4.2:collectionHandlers:vue-next/packages/reactivity/src/collectionHandlers.ts

因为 collectionHandler 处理的对象类型的特殊性,key是object,所以 collectionHandlers 给proxy提供的handler类型很多。

总归来说,就是将各种数据类型分为了三类,分别处理,生成proxy,将数据置为reactive

 最后,可以实践一下,看看reactive的产物,有什么区别?

        const wm1 = new WeakMap();
        const o1 = {},
            o2 = function(){},
            o3 = window;
        wm1.set(o1, 37);
        wm1.set(o2, "azerty");

        const ws = new WeakSet();
        const foo = {};
        const bar = {};
        ws.add(foo);
        ws.add(bar);

        const testCOMMONobject = reactive({
            a: 1,
            b: 'test',
        });
        console.log(testCOMMONobject);
        const testCOMMONarr = reactive(['测试数组', '测试数组1', '测试数组2'])
        console.log(testCOMMONarr)
        const testCOLLECTIONwm = reactive(wm1);
        console.log(testCOLLECTIONwm)
        const testCOLLECTIONws = reactive(ws);
        console.log(testCOLLECTIONws)
        console.log(readonly(testCOMMONobject));
        console.log(markRaw(testCOMMONarr)) // __v_skip: true        

reactive的原理就是生成proxy劫持,在setter时候trigger,在trigger中收集目标并触发effects,最终触发patch。

3.trigger和track是怎么生效的?

import { track, trigger } from './effect';
 track(target, TrackOpTypes.GET, key); 
  trigger(target, TriggerOpTypes.SET, key, value, oldValue?);

from: vue-next/packages/reactivity/src/effect.ts

3.1.哪些操作operations会触发track和trigger?

 当对一个对象进行get、has、iterate的时候,会触发该对象的track,收集依赖到targetMap。对对象进行set、add、delete、clear时会触发trigger,使用target中的deps触发依赖追踪。

3.2.怎么收集保存对象的依赖信息?

我们来看源码,源码先声明了一个targetMap来保存对象的依赖信息:

(不得不说,vue3 也是一本 typescript 高级教程😂

// The main WeakMap that stores {target -> key -> dep} connections.
// Conceptually, it's easier to think of a dependency as a Dep class
// which maintains a Set of subscribers, but we simply store them as
// raw Sets to reduce memory overhead.
type Dep = Set<ReactiveEffect>
type KeyToDepMap = Map<any, Dep>
const targetMap = new WeakMap<any, KeyToDepMap>()

export interface ReactiveEffect<T = any> {
 
// 限制这个接口的实现必须是一个返回值为T(泛型)的函数,https://forum.vuejs.org/t/vue3-0-ts-t/81276
 (): T
  _isEffect: true
  id: number
  active: boolean
  raw: () => T
  deps: Array<Dep>
  options: ReactiveEffectOptions
  allowRecurse: boolean
}

 3.3.track

track(obj, TrackOpTypes.GET,'a');

运行示例,就可以看到target的数据结构:

        const obj = {
            a: '1',
        }
        const targetMap = new WeakMap();
        const depsMap = new Map()
        targetMap.set(obj, depsMap);
        let dep = depsMap.get('a')
        if (!dep) {
            depsMap.set('a', (dep = new Set()))
        }
        let effect = {};
        effect.id = 2;
        effect.allowRecurse = true;
        effect._isEffect = true;
        effect.active = true;
        effect.raw = () => {};
        effect.deps = [];
        effect.options = {};
        dep.add(effect);
        effect.deps.push(dep);
        console.log(targetMap)    

我们通过一个实例:

const obj = {
    a: '1',
}
track(obj, TrackOpTypes.GET, 'a');

看看track的运行过程,看看track是如何收集obj.a的依赖的:

export function track(target: object, type: TrackOpTypes, key: unknown) {
  if (!shouldTrack || activeEffect === undefined) {
    return
  }
  // 查询 targetMap 中是否保存有 obj 的依赖,obj是否被追踪
  let depsMap = targetMap.get(target)
  // 如果 depsMap 不存在,targetMap.set(obj, new Map()),添加obj的依赖追踪
  if (!depsMap) {
    targetMap.set(target, (depsMap = new Map()))
  }
  // 查询targetMap的 key -> obj 中是否有 'a'的依赖追踪,如果没有添加
  let dep = depsMap.get(key)
  if (!dep) {
    // depsMap.set('a', new Set())
    depsMap.set(key, (dep = new Set()))
  }
  // 如果没有 activeEffect,新增
  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.4 trigger

trigger的运行过程。其实是消费targetMap的依赖,找到依赖后,开始执行任务。

const obj = {
    a: '1',
}
trigger(obj, TriggerOpTypes.SET, 'a', '2', '1');

 

4.computed

也是通过effect实现依赖的收集和触发。不同的是 ComputedRefImpl 加入了_dirty 和 _value。_dirty 用来确认当前计算是否结束,是否需要更新计算结果。_value 用来缓存计算结果。
posted on 2020-07-02 16:39  搭错车的小火柴  阅读(816)  评论(0编辑  收藏  举报