Jinuss's blog Jinuss's blog
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

前端可视化
首页
  • 前端文章

    • JavaScript
  • 学习笔记

    • 《JavaScript高级程序设计》
    • 《Vue》
    • 《React》
    • 《Git》
    • JS设计模式总结
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • 概览

  • 响应式系统

  • runtime运行时

    • createApp 介绍
    • runtime-core

    • compiler编译

    • 《Vue3源码》笔记
    • runtime运行时
    东流
    2024-06-05
    目录

    createApp 介绍

    # 概述

    createApp 是使用 vue3 调用的第一个也是必须的入口函数,通过它可以创建一个应用实例,然后我们可以通过返回的实例对象挂载到真实的 DOM 元素上,还可以安装插件等等,一般使用如下

    import { createApp } from "vue";
    import { createPinia } from "pinia";
    import App from "./App.vue";
    const app = createApp(App); // App为根组件
    
    app.use(createPinia()); // 安装Pinia插件
    
    app.mount("#app"); // 挂载
    
    1
    2
    3
    4
    5
    6
    7
    8

    # 深入了解 createApp

    # runtime-dom中的包装

    我们引用的createApp函数定义如下所示,其在node_modules\@vue\runtime-dom

    const createApp = (...args) => {
      const app = ensureRenderer().createApp(...args);
      const { mount } = app;
      app.mount = (containerOrSelector) => {
        const container = normalizeContainer(containerOrSelector); // 通过`document.querySelector`查找并返回 dom 元素
        if (!container) return;
        const component = app._component;
        if (
          !shared.isFunction(component) &&
          !component.render &&
          !component.template
        ) {
          component.template = container.innerHTML;
        }
        container.innerHTML = ""; //清空
        const proxy = mount(container, false, resolveRootNamespace(container)); // resolveRootNamespace 判断container是属于svg还是MathML命名空间
        if (container instanceof Element) {
          container.removeAttribute("v-cloak");
          container.setAttribute("data-v-app", "");
        }
        return proxy;
      };
      return app;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24

    很显然这里只是给app包装了一层,定义了一个mount函数,具体的实现是第一句ensureRenderer().createApp(...args),实际上,这句返回的才是我们所熟知的实例app。通过观察发现,它是调用了runtimeCore的createRenderer方法,参数为rendererOptions,包含patchProp方法和nodeOps

    # patchProp方法

    patchProp主要作用是根据虚拟 DOM 的变化来更新实际的 DOM 元素的属性,它负责处理添加、删除和更新 DOM 元素的属性,实现 Vue 组件的属性动态更新。而patchProp的操作依据就是参数key的值,这里不过多赘述,详见 key 的更新机制

    //伪代码
    const patchProp = (
      el,
      key,
      prevValue,
      nextValue,
      namespace,
      prevChildren,
      parentComponent,
      parentSuspense,
      unmountChildren
    ) => {
      if(key==='class'){
        patchClass()
      }else if(key==='style'){
        patchStyle()
      }else if(isOn(key)){ //key是否是onClick等等
        patchEvent()
      }else if(...){ //主要用于处理设置元素的 CSS 类、排除不应设置为属性的情况,以及正常设置属性的情况
        patchDOMProp()
      }else{
        patchAttr()
      }
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # nodeOps

    nodeOps就是基于 DOM 定义了一些 DOM 节点的操作方法,这些方法将在 vue3 运行 DOM 时被使用,主要如下

    insert: 将一个子节点插入到父节点中,可以指定插入位置。

    remove: 从父节点中移除一个子节点。

    createElement: 创建一个新的 DOM 元素节点。

    createText: 创建一个包含指定文本的文本节点。

    createComment: 创建一个注释节点。

    setText: 设置文本节点的文本内容。

    setElementText: 设置元素节点的文本内容。

    parentNode: 获取节点的父节点。

    nextSibling: 获取节点的下一个兄弟节点。

    querySelector: 在文档中查找匹配指定选择器的第一个元素。

    setScopeId: 设置元素节点的作用域 ID。

    insertStaticContent: 插入静态内容到指定位置。

    # runtime-core中的核心实现

    runtime-core\src\renderer.ts中runtime-core调用createRenderer 函数,会返回一个重载函数baseCreateRenderer,该函数里面定义了许多操作组件的方法,返回如下

    return {
      render,
      hydrate, //ssr 渲染不用管
      createApp: createAppAPI(render, hydrate),
    };
    
    1
    2
    3
    4
    5
    # render方法

    render顾名思义就是用来渲染节点,这也放在其它文章里重点讲解组件的渲染

    const render = (vnode, container, namespace) => {
      if (vnode == null) {
        if (container._vnode) {
          unmount(container._vnode, null, null, true);
        }
      } else {
        patch(
          container._vnode || null,
          vnode,
          container,
          null,
          null,
          null,
          namespace
        );
      }
      if (!isFlushing) {
        isFlushing = true;
        flushPreFlushCbs();
        flushPostFlushCbs();
        isFlushing = false;
      }
      container._vnode = vnode;
    };
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    # createAppAPI方法

    createAppAPI是在runtime-core\src\apiCreateApp.ts中实现的,其伪代码如下。

    export function createAppAPI(render) {
      return function createApp(rootComponent, rootProps=null) {
    
        const context = createAppContext()
        const installedPlugins = new WeakSet()
    
        const app = {
          _uid: uid++,
          _component: rootComponent, // 即最上面的 App 根组件
          _props: rootProps,
          _container: null,
          _context: context,
          _instance: null,
          version,
          get config(){},
          set config(){},
          use(plugin,...options){},
          mixin(){},
          component(name,component){},
          mount(){},
          unmount(){},
          provide(){},
          runWithContext(fn){}
        };
    
        return app;
      };
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28

    首先调用createAppContext定义了上下文环境context,主要用来存放cache数据, 然后声明一个WeakSet集合存储插件,最后定义一个 app 对象并返回。

    当我们通过app实例进行挂载mount时,本质上就是调用的上述 app 中的mount方法, 创建完虚拟节点vnode后,会调用render方法是在baseCreateRenderer中实现的,如上。

     mount(rootContainer, isHydrate, namespace) {
            if (!isMounted) {
              const vnode = createVNode(rootComponent, rootProps); //根据rootComponent 创建虚拟DOM
              vnode.appContext = context; //绑定context
              if (namespace === true) {
                namespace = "svg";
              } else if (namespace === false) {
                namespace = void 0;
              }
              if (isHydrate && hydrate) {
                hydrate(vnode, rootContainer); // ssr 不用管
              } else {
                render(vnode, rootContainer, namespace);
              }
              isMounted = true; // 重置挂载标志
              app._container = rootContainer;
              rootContainer.__vue_app__ = app;
              return getExposeProxy(vnode.component) || vnode.component.proxy;
            }
          },
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    编辑 (opens new window)
    上次更新: 2024/06/06, 09:54:54
    响应系统里的工具函数
    nextTick

    ← 响应系统里的工具函数 nextTick→

    最近更新
    01
    GeoJSON
    05-08
    02
    Circle
    04-15
    03
    CircleMarker
    04-15
    更多文章>
    Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式