Vue3 runtime-core

175 阅读2分钟

禁止转载,侵权必究!

创建 runtime-core 包

runtime-core 不关心运行平台。

runtime-core/package.json

{
  "name": "@vue/runtime-core",
  "module": "dist/runtime-core.esm-bundler.js",
  "types": "dist/runtime-core.d.ts",
  "files": ["index.js", "dist"],
  "buildOptions": {
    "name": "VueRuntimeCore",
    "formats": ["esm-bundler", "cjs"]
  }
}

runtime-core 中需要依赖 @vue/shared 及 @vue/reactivity

pnpm install @vue/shared@workspace @vue/reactivity@workspace --filter @vue/runtime-core

形状标识

通过组合可以描述虚拟节点的类型

@shared/shapeFlags.ts

export const enum ShapeFlags {
  ELEMENT = 1, // 虚拟节点是一个元素
  FUNCTIONAL_COMPONENT = 1 << 1, // 函数式组件
  STATEFUL_COMPONENT = 1 << 2, // 普通组件  // stateful
  TEXT_CHILDREN = 1 << 3, // 儿子是文本的
  ARRAY_CHILDREN = 1 << 4, // 儿子是数组
  SLOTS_CHILDREN = 1 << 5, // 插槽
  TELEPORT = 1 << 6,
  SUSPENSE = 1 << 7,
  COMPONENT_SHOULD_KEEP_ALIVE = 1 << 8,
  COMPONENT_KEPT_ALIVE = 1 << 9,
  COMPONENT = ShapeFlags.STATEFUL_COMPONENT | ShapeFlags.FUNCTIONAL_COMPONENT, // 2 + 4 = 6
}

createVNode 实现

runtime-core/src/vnode.ts

import { isString, ShapeFlags } from "@vue/shared";

// children 数组 字符串 空

export function isVNode(vnode) {
  return vnode.__v_isVnode == true;
}

export function isSameVNode(n1, n2) {
  return n1.type === n2.type && n1.key === n2.key;
}
export function createVNode(type, props = null, children = null) {
  const shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;
  // 虚拟节点要对应真实节点
  const vnode = {
    __v_isVnode: true, // 添加标识是不是vnode
    type,
    props,
    children,
    shapeFlag,
    key: props?.key,
    el: null, // 对应的真实节点
  };
  if (children) {
    let type = 0;
    if (Array.isArray(children)) {
      // [vnode,'文本']
      type = ShapeFlags.ARRAY_CHILDREN;
    } else {
      // 文本
      type = ShapeFlags.TEXT_CHILDREN;
    }
    // vnode.shapeFlag = vnode.shapeFlat | type;
    vnode.shapeFlag |= type; // vnode.shapeFlag = vnode.shapeFlag | type;
  }
  return vnode; // 根据  vnode.shapeFlag  来判断自己的类型和孩子的类型
}

h 实现

h 方法 是返回虚拟节点

runtime-core/src/h.ts

import { isObject } from "@vue/shared";

import { createVNode, isVNode } from "./vnode";
export function h(type, propsOrChildren?, children?) {
  const l = arguments.length;

  // h(type,{})   h(type,h('span')) => h(type,[h('span')])  /    h(type,[])   h(type,'文本')
  if (l == 2) {
    // 对象并且不是数组
    if (isObject(propsOrChildren) && !Array.isArray(propsOrChildren)) {
      if (isVNode(propsOrChildren)) {
        return createVNode(type, null, [propsOrChildren]);
      }
      return createVNode(type, propsOrChildren);
    } else {
      // 数组或者文本
      return createVNode(type, null, propsOrChildren);
    }
  } else {
    if (l > 3) {
      children = Array.from(arguments).slice(2); // 除了前两个都是child
      // h('div',{},'a','b','c') 这样操作第二个参数必须是属性 h('div','e','a','b','c')

      // h('div',{},h('span')) => h('div',{},[h('span')])
    } else if (l === 3 && isVNode(children)) {
      children = [children];
    }
    return createVNode(type, propsOrChildren, children);
    // l == 3
  }
}

createRenderer 实现

createRenderer 可以用户自定义渲染方式; createRenderer 返回的 render 方法 接受参数是虚拟节点和容器

render 方法就是采用 runtime-dom 中提供的方法将虚拟节点转化成对应平台的真实节点渲染到指定容器中。

runtime-core/renderer.ts

export function createRenderer(options) {
  const {
    insert: hostInsert,
    remove: hostRemove,
    patchProp: hostPatchProp,
    createElement: hostCreateElement,
    createText: hostCreateText,
    setText: hostSetText,
    setElementText: hostSetElementText,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
  } = options;
  const patch = (n1, n2, container) => {
    // 初始化和diff算法
  };
  const render = (vnode, container) => {
    if (vnode == null) {
      if (container._vnode) {
        // 说明渲染过了,我才需要进行卸载操作
      }
    } else {
      patch(container._vnode || null, vnode, container); // 初始化和更新
    }
    container._vnode = vnode; // 第一次渲染保存虚拟节点
  };
  return {
    render,
  };
}

创建真实 DOM

runtime-core/renderer.ts => createRenderer 方法

const mountChildren = (children, container) => {
  for (let i = 0; i < children.length; i++) {
    patch(null, children[i], container);
  }
};
const mountElement = (vnode, container) => {
  const { type, props, shapeFlag } = vnode;
  let el = (vnode.el = hostCreateElement(type)); // 创建真实元素,挂载到虚拟节点上;
  if (props) {
    // 处理属性
    for (const key in props) {
      // 更新元素属性
      hostPatchProp(el, key, null, props[key]);
    }
  }
  if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
    // 文本
    hostSetElementText(el, vnode.children);
  } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
    // 多个儿子
    mountChildren(vnode.children, el);
  }
  hostInsert(el, container); // 插入到容器中
};
const patch = (n1, n2, container) => {
  // 初始化和diff算法都在这里喲
  if (n1 == n2) {
    return;
  }
  if (n1 == null) {
    // 初始化的情况
    mountElement(n2, container);
  } else {
    // diff算法
  }
};

卸载 DOM

runtime-core/renderer.ts => createRenderer 方法

const unmount = (vnode) => {
  hostRemove(vnode.el);
};
const render = (vnode, container) => {
  if (vnode == null) {
    if (container._vnode) {
      // 卸载
      +unmount(container._vnode); // 找到对应的真实节点将其卸载
    }
  } else {
    patch(container._vnode || null, vnode, container); // 初始化和更新
  }
  container._vnode = vnode;
};

优化调用方法

runtime-dom/src/index.ts

export const render = (vnode, container) => {
  createRenderer(renderOptions).render(vnode, container);
};
export * from "@vue/runtime-core";