Shuey猫舍

文章摄影项目关于
https://cdn.shuey.fun/images/2022/0314/title_bg.webp

Vue3源码笔记

2022-03-14| 阅读量: | 评论:

  • Web
  • Vue
@Geister

Commit ID: 3401f6b460196ce254a73df05ce571802b365054

先从依赖关系最少的看起来,逐步看完这个第一份提交记录的内容。所以对应的版本才到3.0.0-alpha.1,阅读代码的时候有很多没有被使用的地方。

RuntimeDom

这个应该是vue的入口。

nodeOps定义了一些常用的节点操作,例如创建元素、文本,追加、插入、删除元素,父、邻节点查找,Selector。

teardownVNode定义了vNode的销毁操作,使用handleDelegatedEvent。handleDelegatedEvent最后一个参数给null,可以去除相关的事件监听, 其只负责处理如下事件。

1
const delegateRE = /^(?:click|dblclick|submit|(?:key|mouse|touch).*)$/

顺着handleDelegatedEvent阅读下去,如果同名的事件监听数量为1,移除过程就从document中移除这个代理事件监听(removeGlobalHandler),为0则在创建全局的事件代理(attachGlobalHandler)。同时代码也表明el元素下有属性__events存着事件的处理函数,如果不存在则全局同名事件数量加一。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
function attachGlobalHandler(name: string) {
  const handler = (attachedGlobalHandlers[name] = (e: Event) => {
    const { type } = e
    const isClick = type === 'click' || type === 'dblclick'
    if (isClick && (e as MouseEvent).button !== 0) {
      e.stopPropagation()
      return false
    }
    e.stopPropagation = stopPropagation
    const targetRef: TargetRef = { el: document }
    Object.defineProperty(e, 'currentTarget', {
      configurable: true,
      get() {
        return targetRef.el
      }
    })
    dispatchEvent(e, name, isClick, targetRef)
  })
  document.addEventListener(name, handler)
  eventCounts[name] = 0
}

这段代码是全局注册代理事件的地方,此处的currentTarget设置为document和我平时使用这些事件的第一感觉不一样,原来是在下面的dispatchEvent函数里面重新设置为元素自己targetRef.el = el;

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
function handleNormalEvent(el: Element, name: string, prev: any, next: any) {
  const invoker = prev && prev.invoker // 获取invoker
  if (next) {                          // 如果是处理函数变更,invoker去引用,注册新事件
    if (invoker) {
      prev.invoker = null
      invoker.value = next
      next.invoker = invoker
    } else {
      el.addEventListener(name, createInvoker(next))
    }
  } else if (invoker) { // 如果没有新处理函数,删除旧处理函数
    el.removeEventListener(name, invoker)
  }
}

除被全局监听的事件外,需要元素自己监听的事件交给handleNormalEvent处理,主要在patchData中使用。

patchData的逻辑很简单,传递当前元素、被修改的属性key、更改前后的值、更改前后的VNode、是否是SVG、未卸载的子元素,依据key值的不同,分别执行patchClass, patchStyle, patchEvent(以on开头的属性), patchDOMProp(以domProps开头的属性), 其余的执行patchAttr。

  • patchClass: normalizeClass(value)后将结果给元素(就是平时传递class中使用数组或者对象或者字符串的处理逻辑),不一样的地方是svg调用setAttribute("class", ..),DOM元素调用.className1。
  • patchStyle: 和class差不多普通化的逻辑,列表的处理逻辑是取出里面的元素,扁平化到一个对象。
  • patchDOMProp: 当key是innerHTML或者textContent且有子元素的时候,卸载子元素,更改修改前VNode的ChildrenFlags。然后直接修改元素值。
  • patchAttr: 判断SVG特殊处理xLink,其余设置属性、移除属性即可。

到此为止runtime-dom部分的代码已经全部结束。

Scheduler

这部分代码是全部代码中最简短的部分。

queueJob将判断当前是否在执行任务队列,是立即执行该任务,不是入队列。然后将任务回调也放入队列,判断是否在执行任务队列,不是则调用执行任务队列。

其中借助Promise.resolve()实现nextTick,参考MDN上的文章:深入:微任务与Javascript运行时环境

Observer

实现ref、compute等功能的基础.

工具类

全局锁,提供lock和unlock函数,目前来看作用只在core/componentProps/updateProps中使用,让Props变为临时的可变属性。

LOCKED #TODO

operations.ts定义了操作类型,主要是对应Debugger事件。

state.ts记录了Vue中对象到依赖的关系(target -> key -> dep),raw和observed、raw和immutable之间的关系,以及被标记为不可变或者无响应式的对象集合。

在看autorun部分代码的时候,第一次看比较疑惑createAutorun的实现(把Autorun和autorun看迷糊了!!),之后查找的computed.ts代码的时候才注意到index.ts中有autorun的实现,在调用createAutorun的时候已经被还原为函数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
export function autorun(
  fn: Function,
  options: AutorunOptions = EMPTY_OBJ
): Autorun {
  if ((fn as Autorun).isAutorun) {
    fn = (fn as Autorun).raw
  }
  const runner = createAutorun(fn, options)
  if (!options.lazy) {
    runner()
  }
  return runner
}

createObservable当中使用void 0判断是否为空的时候,表示不解,请参考博文:为什么用「void 0」代替「undefined」; 主要是判断当前是否已经是代理对象或者已经有代理对象,如果有则返回代理对象。如果没有则判断是否可以生成Observe对象,生成新的代理对象。对Set, Map, WeakMap, WeakSet使用collectionHandlers; 结束的时候还添加了一下Observable对象的到key到Dep的关系表。

1
2
3
4
5
6
7
8
const canObserve = (value: any): boolean => {
  return (
    !value._isVue && !value._isVNode &&
    /^\[object (?:Object|Array|Map|Set|WeakMap|WeakSet)\]$/
      .test(Object.prototype.toString.call(value)) &&
    !nonReactiveValues.has(value)
  )
}
@startuml
abstract ComputedGetter {
  -value
  -dirty
  +(): any
  +stop: () => void
}

interface Autorun {
  +(): any
  +isAutorun: true
  +active: boolean
  +raw: Function
  +deps: Array<Dep>
  +scheduler?: Scheduler
  +onTrack?: Debugger
  +onTrigger?: Debugger
}

interface DebuggerEvent {
  runner: Autorun
  target: any
  type: OperationTypes
  key: string | symbol | undefined
}

class funtion {
  createAutorun()
  stop()
  cleanUp()
}

class computed {
  dirty: boolean = true
  value: any = undefined
  runner()
}

ComputedGetter::value -> Autorun
computed::runner -> funtion::createAutorun
Autorun::onTrigger --> DebuggerEvent
Autorun::onTrack --> DebuggerEvent
Autorun::raw --> funtion::createAutorun

funtion::createAutorun ..> Autorun
@enduml

baseHandler && collectionHandler

trigger && tracker

get属性会对应到trigger,set属性会对应到tracker

Core

工具类

1
export const EMPTY_OBJ: { readonly [key: string]: any } = Object.freeze({})

创建一个空对象,此处使用Object.freeze({})创建对象,之后改成Object.create(null).

21
22
23
24
25
26
27
28
29
30
export const isReservedProp = (key: string): boolean => {
  switch (key) {
    case 'key':
    case 'ref':
    case 'slots':
      return true
    default:
      return key.startsWith('nativeOn')
  }
}

判断当前属性是否是保留的属性,包括key、ref、slots之类的Vue自身的关键字,在之后的解析Props中被使用(参见: componentProps.ts,createRender.ts),一般都是用来跳过保留属性.

目前errorHandling.ts在项目中没有被使用,定义了ErrorTypes(生命周期、渲染、原生事件、组件事件)和handleError的函数。

参考

  • https://blog.csdn.net/u012562943/article/details/144004208

  1. SVG的className是SVGAnimatedString,而不是DOM元素的string! ↩︎

WebGL学习

2022-04-25 16:39

Vercel搭建博客

2022-03-13 22:57

Shuey Yuen

Shuey Yuen

百工之人,不恥相師

目录

  • RuntimeDom
  • Scheduler
  • Observer
    • 工具类
    • baseHandler && collectionHandler
    • trigger && tracker
  • Core
    • 工具类
  • 参考

相关文章

  • TypeScript类型体操(基础)
  • TypeScript装饰器(5.0)
  • TypeScript装饰器(旧)
  • Codepage项目开发

分类

  • Web 9
  • WebGL 2
显示全部

聚合标签

TypeScriptDecoratorDesign PatternWASMWebGLAlgolia
显示全部

© 2017-2025 Shuey Yuen/ admin / store

皖ICP备2023022130号-1

🌞 浅色 🌛 深色 🤖️ 自动