Vue 3 Pre-Alpha / vue-next 源码学习之 vue/reactivity/reactive.ts

前言

今天突然回忆自己的前端成长之路,自己为什么要学习各种框架及库源码,学习各种源码有什么好处呢?其实感觉自己的能力或多或少是伴随着一系列的前端源码学习而不断进步的。

当年学习 jQuery 的源码学习到了 extend 对象 merge 及入参 options / default options 、链式调用、如何实现链式调用和如何与各种 Dom 打交道;当年学习 weui 学习到了移动端如何处理 1 像素 border 处理、学习到了可以使用 document.activeElement.scrollIntoViewIfNeeded 来解决 iphone 移动端页面偶发出现键盘灰块问题;通过 Vue 2.x 源码,可以知道在使用 Vue.js 时为什么要定义 key,对数组或者深层对象的 State 对象的修改为什么要使用 Vue.prototype.$set / this.$set 来修改赋值来能更好的触发视图更新;通过 Element UI 源码的学习,可以学习到如何封装一个通用的 Vue 组件,如何使用 Store 的思想来封装一个复杂的多功能 Vue 组件。

费话不多说,先回到正题,现 Vue 3 Pre-Alpha 源码已出,必须尝尝新,学习一下其中的新思想,看有没有什么意想不到的收获。Vue 是数据驱动,就先由其双向绑定的思想开始,先学习 reactive 响应式的数据处理。

源码分析

注意带【】符号的为关键代码,关键代码能体现作者的最关键最基础的实现思路

import { isObject, toTypeString } from '@vue/shared' // 一些工具函数,一个是判断是否为Object,一个是Object.prototype.toString.call,平时我也们可以用到自己的项目中哈
import { mutableHandlers, readonlyHandlers } from './baseHandlers' // 定义一个 Proxy,如:new Proxy({}, handler) 需要传入 handler
import {
  mutableCollectionHandlers,
  readonlyCollectionHandlers
} from './collectionHandlers' // 定义一个 Proxy,如:new Proxy({}, handler) 需要传入 handler
import { ReactiveEffect } from './effect' // 一些勾子回调函数
import { UnwrapRef, Ref } from './ref' // Ref 是相对 Reactive 来说的,reactive 传参需要是object,而 Ref 数据没有限制,都是返回一个被 Proxy 的代理
import { makeMap } from '@vue/shared' // 在 Vue 2.x 里就出现的工具,就是生成一个 key value 对,传入的是用 “,” 隔开的一个多 key 的字符串

// WeakMap 及 WeakSet 都是好东西,自带内存垃圾回收机制。如 WeakMap 中,key 如果设为 null 值,相应的 value 也没了。
// 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.
export type Dep = Set<ReactiveEffect> // 一系列的勾子函数,用于响应被观测的数据的变化
export type KeyToDepMap = Map<string | symbol, Dep> // 把 Dep 用 key 来 Map 起来
export const targetMap = new WeakMap<any, KeyToDepMap>() // 再把 KeyToDepMap 用 key 来 WeakMap 起来,这就有意思了,因为 WeekMap的内存垃圾回收机制,any 消失了,KeyToDepMap 是会被自动释放的

// WeakMaps that store {raw <-> observed} pairs. // Readonly 对象也是 Reactive 对象的一种
const rawToReactive = new WeakMap<any, any>() // 把 raw 数据与被 Proxy 代理生成的 Reactive 对象组成 key-value set 到 WeakMap 中
const reactiveToRaw = new WeakMap<any, any>() // 把被 Proxy 代理生成的 Reactive 对象与 raw 数据组成 key-value set 到 WeakMap 中
const rawToReadonly = new WeakMap<any, any>() // 把 raw 数据与被 Proxy 代理生成的 Readonly 对象组成 key-value set 到 WeakMap 中
const readonlyToRaw = new WeakMap<any, any>() // 把被 Proxy 代理生成的 Readonly 对象与 raw 数据组成 key-value set 到 WeakMap 中

// WeakSets for values that are marked readonly or non-reactive during
// observable creation.
const readonlyValues = new WeakSet<any>() // 把被 Proxy 代理生成的 Readonly 对象 push 到 WeakSet 中
const nonReactiveValues = new WeakSet<any>() // 被 Proxy 代理生成的 nonReactive 对象 push 到 WeakSet 中

const collectionTypes = new Set<Function>([Set, Map, WeakMap, WeakSet]) // 用于收集其他类型的 非 baseHandlers 的 handler 构造函数
const isObservableType = /*#__PURE__*/ makeMap( // 可被 Observe 的对象类型如下
  ['Object', 'Array', 'Map', 'Set', 'WeakMap', 'WeakSet']
    .map(t => `[object ${t}]`)
    .join(',')
)

const canObserve = (value: any): boolean => { // 判断值是还可被 Observe
  return (
    !value._isVue &&
    !value._isVNode &&
    isObservableType(toTypeString(value)) &&
    !nonReactiveValues.has(value)
  )
}

// only unwrap nested ref
type UnwrapNestedRefs<T> = T extends Ref ? T : UnwrapRef<T> // 解套

export function reactive<T extends object>(target: T): UnwrapNestedRefs<T> // 定义 reactive 构造函数
export function reactive(target: object) { // 创建一个 Reactive Object
  // if trying to observe a readonly proxy, return the readonly version.
  if (readonlyToRaw.has(target)) {
    return target
  }
  // target is explicitly marked as readonly by user
  if (readonlyValues.has(target)) {
    return readonly(target)
  }
  return createReactiveObject(
    target,
    rawToReactive,
    reactiveToRaw,
    mutableHandlers,
    mutableCollectionHandlers
  )
}

export function readonly<T extends object>( // 创建一个 Readonly 版本的 Reactive Object
  target: T
): Readonly<UnwrapNestedRefs<T>> {
  // value is a mutable observable, retrieve its original and return
  // a readonly version.
  if (reactiveToRaw.has(target)) {
    target = reactiveToRaw.get(target)
  }
  return createReactiveObject(
    target,
    rawToReadonly,
    readonlyToRaw,
    readonlyHandlers,
    readonlyCollectionHandlers
  )
}

function createReactiveObject( // 创建一个 Reactive Object
  target: any,
  toProxy: WeakMap<any, any>,
  toRaw: WeakMap<any, any>,
  baseHandlers: ProxyHandler<any>,
  collectionHandlers: ProxyHandler<any>
) {
  if (!isObject(target)) {
    if (__DEV__) {
      console.warn(`value cannot be made reactive: ${String(target)}`)
    }
    return target
  }
  // target already has corresponding Proxy
  let observed = toProxy.get(target)
  if (observed !== void 0) {
    return observed
  }
  // target is already a Proxy
  if (toRaw.has(target)) {
    return target
  }
  // only a whitelist of value types can be observed.
  if (!canObserve(target)) {
    return target
  }
  const handlers = collectionTypes.has(target.constructor)
    ? collectionHandlers
    : baseHandlers
  observed = new Proxy(target, handlers) // 【reactive 的关键代码】 createReactiveObject 关键代码,使用 Proxy
  toProxy.set(target, observed)
  toRaw.set(observed, target)
  if (!targetMap.has(target)) {
    targetMap.set(target, new Map())
  }
  return observed
}

export function isReactive(value: any): boolean { // 判断是否为 Reactive 对象
  return reactiveToRaw.has(value) || readonlyToRaw.has(value)
}

export function isReadonly(value: any): boolean { // 判断是否为 Readonly 对象
  return readonlyToRaw.has(value)
}

export function toRaw<T>(observed: T): T { // 获得原始数据
  return reactiveToRaw.get(observed) || readonlyToRaw.get(observed) || observed
}

export function markReadonly<T>(value: T): T { // 使用值变为Readonly,其实就是把值放到 readonlyValues WeakSet 中
  readonlyValues.add(value)
  return value
}

export function markNonReactive<T>(value: T): T { // 使用值变为 NonReactive,其实就是把值放到 nonReactiveValues WeakSet 中
  nonReactiveValues.add(value)
  return value
}

总结

因为是 Pre-Alpha 版本,源码还在不断变更中,但我们由这版的代码就可以学习到很多新的思想。源码中我们可以看到不管上工程构建上使用的 lerna.js 多包管理,还是类型检查及语言选择上抛弃 Vue 2.x 中使用的 Flow 转投 Typescript ,到 ES6 中 MapWeakMapSetWeakSet 用得如此之溜,Vue 作者男神尤大大尚且如此努力学习与进步,我们还有什么借口去慵懒而不学习呢 🙂

作者: 博主

Talk is cheap, show me the code!

发表评论

电子邮件地址不会被公开。

Captcha Code