likes
comments
collection
share

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

作者站长头像
站长
· 阅读数 39

runtime运行时 -h函数

模块总览

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

到这一篇文章,响应式系统部分就全部结束了,接下来开始另一个核心模块runtime运行时.

在该专栏第一篇文章框架设计概念中提到: vue在运行时主要的工作时将虚拟DOM渲染为真实DOM,在这个过程中主要涉及两个函数:

  • h:生成虚拟DOM
  • render:将虚拟DOM渲染

什么是虚拟DOM

虚拟DOM是用来描述真实DOM的一个js对象.这个对象中有许多创建真实DOM的必要属性,这些属性描述了对应真实DOM的结构.然后就可以根据虚拟DOM创建出真实DOM.

那么为什么需要虚拟DOM呢?

因为操作虚拟DOM的性能要比操作真实DOM的性能更快,在复杂更新场景下,这将节省很多性能

h函数

vue中虚拟DOM的生成依赖于h函数,该函数可以根据参数生成对应的虚拟DOM,

  1. 第一个参数: 必填,可以是字符串(原生标签)也可以是Vue的组件定义
  2. 第二个参数: 可选,标签或者组件的各种属性
  3. 第三个参数: 可选,标签或者组件的子节点

那么h函数是如何将这些参数转换成虚拟DOM的呢?vue中的虚拟DOM到底长什么样子呢?让我们带着问题,到源码中一探究竟吧!

debugger

使用如下测试用例:

<!DOCTYPE html>
<html>
  <head>
    <script src="../../dist/vue.global.js"></script>
  </head>
  <body>
    <div id="app"></div>
    <script>
      const { h } = Vue
      debugger
      const vnode = h('div', { class: { test: true } }, 'hello') // 关注点: h函数如何生成虚拟DOM
      console.log('vnode:', vnode)
    </script>
  </body>
</html>

该测试用例只有一个关注点: h函数如何生成虚拟DOM

关注点1:创建虚拟DOM

  1. open with live server打开测试用例,进入debugger

  2. 我们知道h函数的传参形式很灵活,h函数首先将参数统一处理,将type,props,children对应起来,然后传递给createNode

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

1.  进入`createVNode`,在这里先对`props`中的`class和style`属性进行处理,我们知道vue对这两个属性做了增强,以支持数组,对象的赋值形式,增强就是在这里做的

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

  1. 进入normalizeClass,通过循环拼接出类名,然后返回

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

  1. 处理完props中的classstyle,接下来会根据type创建一个变量shapeFlag,这个变量很重要,表示虚拟DOM的类型,我们这里对应的就是ShapeFlags.Element类型,只要记得是Element就好

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

  1. 之后进入createBaseVNode中,这里就是真正创建虚拟DOM的地方,首先定义了一个对象,这个对象就是创建的虚拟DOM

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

  1. 然后进入normalizeChildren函数,对children进行处理:在这个函数中根据children的类型,重新赋值了shapFlag

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

  1. 这里根据children的类型,重新赋值了虚拟DOMshapeFlag(一个数字),通过按位或操作,使得shapFlag技能表示vnode的类型,也能表示vnode的子元素类型

vue3源码阅读与实现: runtime运行时-h模块在该专栏第一篇文章框架设计概念中提到: `vue`在运行时主要的工

到此,返回这个虚拟DOM,h函数就执行完毕了

总结

h函数主要做了三件事情:

  1. 根据容器和子元素的类型计算该虚拟domshapeFlag,该属性非常重要,在之后的渲染环节会根据shapeFlag不同而进行不同的渲染逻辑
  2. 增强class,style
  3. 创建虚拟DOM对象

实现h函数

定义类型

ShapeFlags中定义了vnodechildren的类型:

一共包括两种vnode,三种children(对源码中类型有删减)

这在之后的渲染中非常重要

在在packages/shared/src/shapeFlags.ts

export const enum ShapeFlags {
  // vnode类型
  ELEMENT = 1, // 普通标签
  FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件
  STATEFUL_COMPONENT = 1 << 2, // 有状态组件
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT, // 有状态 | 函数式组件

  //children类型
  TEXT_CHILDREN = 1 << 3, // Text类型
  ARRAY_CHILDREN = 1 << 4, // Array类型
  SLOTS_CHILDREN = 1 << 5, // 插槽类型
}

除了上述的几种vnode类型,还有需定义一些其他的类型,如文本节点,代码片段,注释,这里只额外定义了三种

packages/runtime-core/src/vnode.ts

// 定义三种虚拟DOM类型
export const Text = Symbol("Text");
export const Comment = Symbol("Comment");
export const Fragment = Symbol("Fragment");

h函数

这个函数中主要处理参数:

packages/runtime-core/src/h.ts

import { isArray, isObject, isVnode } from "@vue/shared";
import { createVnode } from "./vnode";

export function h(type: any, propsOrChildren: any, children: any) {
  // 处理参数
  const l = arguments.length;
  if (l === 2) {
    // 只有两个参数,第二个参数可能是children也可能是props也有可能是vndoe
    const isProps = isObject(propsOrChildren) && !isArray(propsOrChildren);
    if (isProps) {
      if (isVnode(propsOrChildren)) {
        return createVnode(type, null, [propsOrChildren]);
      }
      return createVnode(type, propsOrChildren);
    } else {
      return createVnode(type, null, propsOrChildren);
    }
  } else {
    if (l > 3) {
      children = [...arguments].slice(2);
    } else if (l === 3 && isVnode(children)) {
      children = [children];
    }
    return createVnode(type, propsOrChildren, children);
  }
}

packages/shared/src/index.ts中添加isVnode方法:

export const isVnode = (v: any): v is Vnode => {
  return v && v.__V_isVnode === true;
};

createVnode函数

这个函数主要增强classstyle同时根据type确定shapeFlag的值

packages/runtime-core/src/vnode.ts

export function createVnode(type: any, props: any, children?: any) {
  // 增强class和style
  if (props) {
    const { class: klass } = props;
    if (klass) {
      props.class = normalizeClass(klass);
    }
  }
  // 根据type创建shapflag
  const shapeFlag = isString(type) // string类型表示是普通标签
    ? ShapeFlags.ELEMENT
    : isObject(type)
      ? ShapeFlags.STATEFUL_COMPONENT //如果是对象,就是组件
      : 0;
  return createBaseVnode(type, props, children, shapeFlag);
}
/**
 * @message: 增强class处理,以便支持class的多种写法: 字符串,数组,对象
 */
function normalizeClass(klass: any): String {
  let res = "";
  if (isString(klass)) {
    return klass;
  }
  if (isArray(klass)) {
    res = klass.reduce((acc, curr) => {
      acc += ` ${normalizeClass(curr)}`;
      return acc;
    }, "");
  } else if (isObject(klass)) {
    res = Object.entries(klass).reduce((acc, [key, value]) => {
      if (value) {
        acc += key;
      }
      return acc;
    }, "");
  }
  return res.trim();
}

createBaseVnode函数

这个函数主要用来创建vnode然后根据children的类型,修改shapFlag的值

packages/runtime-core/src/vnode.ts

/**
 * @message: 创建vnode
 */ 
function createBaseVnode(
  type: any,
  props: any,
  children: any,
  shapeFlag: number
) {
  // 生成vnode
  const vnode: Vnode = {
    __v_isVnode: true,
    type,
    props: props,
    children,
    shapeFlag,
  };

  normalizeChildren(vnode, children);
  return vnode;
}

/**
 * @message: 根据children类型,设置shapeFlag的值
 */
function normalizeChildren(vnode: Vnode, children: any) {
  let type = 0;
  if (children == null) {
    children = null;
  } else if (isArray(children)) {
    type = ShapeFlags.ARRAY_CHILDREN;
  } else if (isObject(children)) {
  } else if (isFunction(children)) {
  } else {
    vnode.children = String(children);
    // 为 type 指定 Flags
    type = ShapeFlags.TEXT_CHILDREN;
  }
  // 修改 vnode 的 chidlren
  vnode.children = children;
  // 按位或赋值,赋值前shapeFlag表示的是vnode的类型,通过或运算,shapeFlag就能同时表示其子元素的类型了
  vnode.shapeFlag |= type;
}

总结

h函数的整体逻辑并不复杂,主要是一些虚拟DOM属性的处理,需要关注的就是shapFlag的处理过程,这个属性将在render部分重点使用

转载自:https://juejin.cn/post/7401315308005179432
评论
请登录