• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

编写Vue3初始化流程

武飞扬头像
陪我去看海
帮助116

前言

又是学习Vue的一天,也是想知道createApp做了什么的一天,演变成想知道Vue整个初始化的过程的一天。

入口

const App = {
  render() {
    // ui
    return h(
      "div",
      {
        id: "root",
        class: ["red", "blue"],
      },
      [h("p", { class: "red" }, "hi"), h("p", { class: "blue" }, "mini-vue")]
    );
  },

  setup() {
    // composition API

    return {
      msg: "VueVueVue",
    };
  },
};

const rootContainer = document.querySelector("#app");
createApp(App).mount(rootContainer);

这里我们能看见createApp, mounth,但是不知道它内部都做了什么,这就来写一个初始化的流程出来。

  1. 可以看到 createApp 接收一个对象,返回一个对象并且有一个mount函数
  2. mount函数接收一个根元素

createApp & mount

function createApp(rootComponent) {
  return {
    mount(rootContainer) {
      // component 转换成 vnode
      const vnode = createVNode(rootComponent);
      // 所有逻辑操作,都基于 vnode 做处理
      render(vnode, rootContainer);
    },
  };
}
  • createApp 内,只做一件事,就是 return 一个对象带有 mount 方法的对象
  • mount 中,会先将组件转换成虚拟DOM,然后之后所有的操作都会基于虚拟DOM执行,主要逻辑就是render函数,它将根组件挂载到根元素上

render

function render(vnode, container) {
  // patch
  patch(vnode, container);
}

render 中只调用了patch,这是因为挂载的时候,不仅仅是组件,还有element和text,所以patch方法就是用来判断是哪种类型的vnode需要做处理

patch

function patch(vnode, container) {
  // 处理element processElement
  // console.log(vnode.type);
  if (typeof vnode.type === "string") {
    processElement(vnode, container);
  } else if (isObject(vnode.type)) {
    // 处理组件 processComponent
    processComponent(vnode, container);
  }
}

Type为对象时候,走处理组件逻辑(processComponent),为字符串的时候,走处理元素的处理逻辑(processElement

processComponent

function processComponent(vnode: any, container: any) {
  // 挂载component
  mountComponent(vnode, container);
}

内部调用了mountComponent方法,用来对组件进行处理

mountComponent

function mountComponent(vnode: any, container) {
  // 创建组件实例,这个实例对象会存储一些组件上的属性 如:props,slots
  const instance = createComponentInstance(vnode);
  // 处理组件
  setupComponent(instance);
  setupRenderEffect(instance, container);
}

在这个方法中,首先会创建一个组件实例,然后后面对组件的处理都是基于这个实例

createComponentInstance

function createComponentInstance(vnode) {
  const component = {
    vnode,
    type: vnode.type,
  };
  return component;
}

createComponentInstance中,对组件做了一个实例化操作,本质就是重新包装了一下vnode,方便后续处理

setupComponent

function setupComponent(instance) {
  /* 
        初始化:
        1. initProps
        2. initSlots()
        3. 调用setup
    */
  setupStatefulComponent(instance);
}

setupComponent方法是初始化的核心,也就是调用所有初始化API的地方,这里就只看初始化节点(ps:其他props,slots暂不考虑)

setupStatefulComponent

function setupStatefulComponent(instance: any) {
  // {vnode: {type: App,props, children}}
  // const App = {setup(){}}
  // 拿到组件
  const Component = instance.type;
  const { setup } = Component;
  if (setup) {
    // function(render) or object(data)
    const setupResult = setup();
    handleSetupResult(instance, setupResult);
  }
}

setupStatefulComponent中就是为了拿到组件的setup函数的返回值(即h函数的返回值 vnode),使用handleSetupResult函数将这个vnode挂载到instance

handleSetupResult

function handleSetupResult(instance, setupResult: any) {
  // function or object
  if (typeof setupResult === "object") {
    instance.setupState = setupResult;
  }
  finishComponentSetup(instance);
}

将最后操作完成后的组件实例instance传入finishComponentSetup

finishComponent

function finishComponentSetup(instance: any) {
  const Component = instance.type;
  // 判断用户是否提供了render函数
  instance.render = Component.render;
}

当用户提供了render函数的时候,就使用用户提供的

setupRenderEffect

function setupRenderEffect(instance: any, container) {
  // 虚拟节点树
  const subTree = instance.render();
  patch(subTree, container);
}

当所有操作完成后,通过render拿到所有需要挂载的虚拟节点树,通过patch递归操作,因为每一次挂载节点都需要判断节点类型,类型不同,不同处理

processElement

function processElement(vnode, container) {
  // init -> update
  mountElement(vnode, container);
}

这里处理与组件处理类似

mountElement

function mountElement(vnode: any, container: any) {
  const { type, props, children } = vnode;
  const el = document.createElement(type);
  // children -> string or array
  if (typeof children === "string") {
    el.textContent = children;
  } else if (Array.isArray(children)) {
    // vnode,挂载children在父元素上
    mountChildren(vnode, el);
  }
  // props -> object
  for (const key in props) {
    if (Object.prototype.hasOwnProperty.call(props, key)) {
      const val = props[key];
      el.setAttribute(key, val);
    }
  }
  el.setAttribute("id", "root");
  container.append(el);
}

这里首先根据标签类型,创建dom元素,将children挂载到这个dom元素上,且将props属性名称与值,添加到这个dom元素上,这里当children是数组类型时,特殊处理

mountChildren

function mountChildren(vnode, container) {
  vnode.children.forEach((v) => {
    patch(v, container);
  });
}

这里的核心逻辑就是patch方法,每一次需要挂载节点的时候,都需要知道当前要挂载节点的类型(组件,元素,文本),因为不同节点类型,不同处理

总结

以上大概就是一个Vue初始化的全过程,我们可以知道:

  1. 所有的操作都是基于虚拟DOM做处理
  2. 最核心就是patch函数,主要作用就是用来,分发不同类型节点给对应的API做处理
  3. Vue将每个函数都抽离的很细致,做到了每个函数只做一件事,遵循职责单一原则,值得学习

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanffaab
系列文章
更多 icon
同类精品
更多 icon
继续加载