vue源码

发布于:2025-03-04 ⋅ 阅读:(11) ⋅ 点赞:(0)

文章目录

      • vue2与vue3的区别
        • 1.源码组织上的变化
        • 2.组合式API
        • 3.运作机制的变化
      • 组件渲染
        • 根组件模板编译
        • 对象组件渲染成真实DOM
          • vue3初始化根组件
          • 创建vnode
          • 渲染成真实的组件

reactivity: 响应式系统
runtime-core:与平台无关的运行时核心 (可以创建针对特定平台的运行时 - 自定义渲染器)
runtime-dom: 针对浏览器的运行时。包括DOM API,属性,事件处理等
runtime-test:用于测试server-renderer:用于服务器端渲染
compiler-core:与平台无关的编译器核心compiler-dom: 针对浏览器的编译模块compiler-ssr: 针对服务端渲染的编译模块compiler-sfc: 针对单文件解析size-check:用来测试代码体积
template-explorer:用于调试编译器输出的开发工具
shared:多个包之间共享的内容vue:完整版本,包括运行时和编译器

vue2与vue3的区别

1.源码组织上的变化

vue2中,所有的源码都存在src目录下complier是模板编译的相关代码,core是与平台无关的通用运行时代码,platforms是平台专有代码,shared是共享工具代码。
vue3相对于vue2使用了monorepo的方式进行包管理,每个包独立负责一块核心功能的实现,有各自的API,类型定义和测试,使得vue3源码职责更加清晰。
compiler-core负责与平台无关层的渲染器底层,对外提供统一调用的函数,没不通过完整的测试用例保障功能的稳定性,complier-dom和complier-ssr依托于complier-core分别实现浏览器和服务端的渲染器上层逻辑。

2.组合式API

组合式API是一系列API的集合,使我们可以使用函数而不是声明选项的方式书写vue组件。在大型项目中,会产生大量状态逻辑的维护甚至跨组件的逻辑复用,更适合用组合式API。

3.运作机制的变化

之前通过new vue()来创建vue对象的方式已经变成了createApp,在响应式部分也由原来object.defineProperty改成了现在的 Proxy API 实现,针对响应式依赖收集的内容,在 Vue 2.x版本中是收集了 watcher ,而到了 Vue 3中则成了 effect 。

组件渲染

根组件模板编译

.vue类型的文件无法在web端直接加载,我们通常会在weback的编译阶段,通过vue-loader编译生成组件相关的 js 和 css,并把 template 部分编译转换成 render函数添加到组件对象的属性中.

对象组件渲染成真实DOM
vue3初始化根组件

createApp过程

export const createApp=(...args)=>{

  const app =ensureRenderer().createApp(...args);
    //....  
  return app;
 }

ensureRenderer().createApp(…args) 这个链式函数执行完成后肯定返回了 mount 函数,ensureRenderer 就是构造了一个带有createApp 函数的渲染器 renderer 对象.

function ensureRenderer() {
  // 如果 renderer 有值的话,那么以后都不会初始化了
  return (
    renderer ||
    (renderer = createRenderer(rendererOptions)
  )
}

// renderOptions 包含以下函数:

const renderOptions = {
  createElement,
  createText,
  setText,
  setElementText,
  patchProp,
  insert,
  remove,
}

这里返回的 renderer 对象,可以认为是一个跨平台的渲染器对象,针对不同的平台,会创建出不同的 renderer 对象,上述是创建浏览器环境的 renderer 对象,对于服务端渲染的场景,则会创建 server render 的 renderer

let enabledHydration = false
function ensureHydrationRenderer(){
   renderer = enabledHydration
    ? renderer
    : createHydrationRenderer(rendererOptions)
  enabledHydration = true
  return renderer
}

createRenderer返回的对象

export function createRenderer(options) {
  // ...
  // 这里不介绍 hydrate 模式
  return {
    render,
    hydrate,
    createApp: createAppAPI(render, hydrate),
  }
}

renderer 对象上包含了 createApp 和 render 方法
createApp方法

function createAppAPI(render, hydrate) {
  // createApp createApp 方法接收的两个参数:根组件的对象和 prop
  return function createApp(rootComponent, rootProps = null) {
    const app = {
      // ... 省略很多不需要在这里介绍的属性
      _component: rootComponent,
      _props: rootProps,
      mount(rootContainer, isHydrate, isSVG) {
        // ...
      }
    }
    return app
  }
}
 

入口文件 createApp 真正执行的内容就是这里的 createAppAPI 函数中的 createApp 函数
该函数接收了 组件作为根组件 rootComponent
返回了一个包含 mount 方法的 app 对象
mount的内部实现

mount(rootContainer, isHydrate, isSVG) {
  if (!isMounted) {
    // ...
    // 1. 创建根组件的 vnode
    const vnode = createVNode(
      rootComponent,
      rootProps
    )
    
    // 2. 渲染根组件
    render(vnode, rootContainer, isSVG)
    isMounted = true
  }
}

一个组件想要真正渲染生成DOM的步骤
1.创建vnode
2.渲染vnode
3.生成DOM

创建vnode

vnode节点就是将真实的DOM以普通对象形式的数据结构来表达,简化了很多DOM中的内容

const vnode={
  type:"div",
  props:{
  'class':'helloWorld'
  },
  children:'helloWorld'
}

根节点如何被创建成一个vnode,根据下列createVNode函数

// packages/runtime-core/src/vnode.ts
function createBaseVNode(...) {
 const vnode = {
   type,
   props,
   key: props && normalizeKey(props),
   children,
   component: null,
   shapeFlag,
   patchFlag,
   dynamicProps,
   dynamicChildren: null,
   // ... 一些其他属性
 }
 // ...
 return vnode
}
function createVNode(type, props = null, children = null) {
 if (props) {
   // 如果存在 props 则需要对 props 进行一些处理,这里先省略
 }
 // ...
 // 处理 shapeFlag 类型
 const shapeFlag = isString(type)
   ? ShapeFlags.ELEMENT
   : __FEATURE_SUSPENSE__ && isSuspense(type)
   ? ShapeFlags.SUSPENSE
   : isTeleport(type)
   ? ShapeFlags.TELEPORT
   : isObject(type)
   ? ShapeFlags.STATEFUL_COMPONENT
   : isFunction(type)
   ? ShapeFlags.FUNCTIONAL_COMPONENT
   : 0
 
 // ...
 return createBaseVNode(
   type,
   props,
   children,
   patchFlag,
   dynamicProps,
   shapeFlag,
   isBlockNode,
   true
 )
}
                                                                                                                    

根据上述代码,当进行根组件渲染的时候,createVNode的第一个入参 type 是App 对象,也就是一个 Object,所以得到的 shapeFlag 的值是 STATEFUL_COMPONENT,代表的是一个有状态组件对象。
也就是说,现在为止,vue完成了对根组件Vnode对象的创建。
引入vnode,可以把渲染过程抽象化,从而使组件的抽象能力得到提升
但是,使用vnode并不意味着不用操作DOM了

渲染成真实的组件

回到mount函数中,进行对vnode的渲染工作

render(vnode,rootContainer)

这里的render函数是在调用createAppAPI时传进来的,而createAppAPI是在创建renderer渲染器的时候调用的

render函数的实现

// packages/runtime-core/src/renderer.ts
const render = (vnode, container) => {
  if (vnode == null) {
    // 如果 vnode 不存在,表示需要卸载组件
    if (container._vnode) {
      unmount(container._vnode, null, null, true)
    }
  } else {
    // 否则进入更新流程(初始化创建也是特殊的一种更新)
    patch(container._vnode || null, vnode, container)
  }
  // 缓存 vnode
  container._vnode = vnode
}

对于初始化根组件的过程中,传入了一个根组件的 vnode 对象,所以这里会执行 patch 相关的动作。patch 本意是补丁的意思,可以理解成为更新做一些补丁的活儿,其实初始的过程也可以看作是一个全量补丁,一种特殊的更新操作

// packages/runtime-core/src/renderer.ts
function patch(n1,n2,container = null,anchor = null,parentComponent = null) {
  // 对于类型不同的新老节点,直接进行卸载
  if (n1 && !isSameVNodeType(n1, n2)) {
    anchor = getNextHostNode(n1)
    unmount(n1, parentComponent, parentSuspense, true)
    n1 = null
  }
  // 基于 n2 的类型来判断
  // 因为 n2 是新的 vnode
  const { type, shapeFlag } = n2;
  switch (type) {
    case Text:
       // 处理文本节点
      break;
    // 其中还有几个类型比如: static fragment comment
    default:
      // 这里就基于 shapeFlag 来处理
      if (shapeFlag & ShapeFlags.ELEMENT) {
        // 处理普通 DOM 元素
        processElement(n1, n2, container, anchor, parentComponent);
      } else if (shapeFlag & ShapeFlags.COMPONENT) {
        // 处理 component
        processComponent(n1, n2, container, parentComponent);
      } else if {
        // ... 处理其他元素
      }
  }
}

初始渲染主要做的两件事:渲染组件生成subTree,把subTree挂载到container中