Vue3源码学习之路-实现runtime-core

createRenderer()

创建一个自定义渲染器。通过提供平台特定的节点创建以及更改 API,你可以在非 DOM 环境中也享受到 Vue 核心运行时的特性。

render

用于编程式地创建组件虚拟 DOM 树的函数。

h

创建虚拟 DOM 节点 (vnode)。h用法大全

VNode标识

类型比对
packages/shared/src/shapeFlags.ts

// 标识
export const enum ShapeFlags {
  ELEMENT = 1,
  FUNCTIONAL_COMPONENT = 1 << 1,
  STATEFUL_COMPONENT = 1 << 2,
  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
}

后续可以使用 | 运算符进行多种类型存储,使用 & 运算符进行判断是否包含某一种类型( a & b > 0 )

创建虚拟节点

packages/runtime-core/src/vnode.ts

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

export const Text = Symbol('Text');

export function isVNode(value) {
  return !!(value && value.__v_isVnode);
}

/**
 * 创建虚拟节点
 * @param type 虚拟节点类型
 * @param props 属性
 * @param children 子节点
 */
export function createVNode(type, props, children = null) {
  let shapeFlag = isString(type) ? ShapeFlags.ELEMENT : 0;

  // 虚拟dom,可以跨平台,性能好
  const vnode = {
    __v_isVnode: true, // 是否是虚拟节点
    shapeFlag, // 类型标识
    type, // 节点类型
    props, // 属性
    children, // 子节点
    key: props?.key, // key
    /**
     * 对应的真实节点,后续diff算法比对两个vnode时会替换新的属性值,并更新el
     */
    el: null,
  };

  if (children) {
    let type = 0;

    if (isArray(children)) {
      type = ShapeFlags.ARRAY_CHILDREN;
    } else {
      children = String(children);
      type = ShapeFlags.TEXT_CHILDREN;
    }

    // 通过位运算将当前vnode类型及子节点类型存储起来
    vnode.shapeFlag |= type;
  }

  return vnode;
}

虚拟节点不用考虑平台兼容,并且可以将虚拟节点利用js存储并进行比对后再渲染真实dom,不用频繁操作dom元素,性能更好

h方法

packages/runtime-core/src/h.ts

/*
// type only
h('div')

// type + props
h('div', {})

// type + omit props + children
// Omit props does NOT support named slots
h('div', []) // array
h('div', 'foo') // text
h('div', h('br')) // vnode
h(Component, () => {}) // default slot

// type + props + children
h('div', {}, []) // array
h('div', {}, 'foo') // text
h('div', {}, h('br')) // vnode
h(Component, {}, () => {}) // default slot
h(Component, {}, {}) // named slots

// named slots without props requires explicit `null` to avoid ambiguity
h(Component, null, {})
**/

import { isArray, isObject } from '@vue/shared';
import { createVNode, isVNode } from './vnode';

export function h(type, propsOrChildren?, children?) {
  const l = arguments.length;

  if (l === 2) {
    if (isObject(propsOrChildren) && !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.prototype.slice.call(arguments, 2);
    } else if (l === 3 && isVNode(children)) {
      children = [children];
    }
    return createVNode(type, propsOrChildren, children);
  }
}

h方法对创建虚拟节点操作进行了二次封装,使用法变得多种多样

挂载及卸载DOM节点

packages/runtime-core/src/renderer.ts

import { isString, ShapeFlags } from '@vue/shared';
import { createVNode, isSameVNode, Text } from './vnode';

export function createRenderer(renderOptions) {
  let {
    insert: hostInsert,
    createElement: hostCreateElement,
    createText: hostCreateText,
    remove: hostRemove,
    setElementText: hostSetElementText,
    setText: hostSetText,
    querySelector: hostQuerySelector,
    parentNode: hostParentNode,
    nextSibling: hostNextSibling,
    patchProp: hostPatchProp,
  } = renderOptions;

  const normalize = (child, i) => {
    if (isString(child[i])) {
      let vnode = createVNode(Text, null, child[i]);
      child[i] = vnode;
      return child[i];
    }
    return child[i];
  };

  // 递归挂载子节点
  const mountChildren = (children, container) => {
    for (let i = 0; i < children.length; i++) {
      let child = normalize(children, i);
      patch(null, child, container);
    }
  };

  const mountElement = (vnode, container) => {
    let { type, props, children, shapeFlag } = vnode;
    // 挂载真实dom到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, children);
    } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
      mountChildren(children, el);
    }

    // 插入真实dom到容器中
    hostInsert(el, container);
  };

  const processText = (n1, n2, container) => {
    if (n1 === null) {
      hostInsert((n2.el = hostCreateText(n2.children)), container);
    } else {
      // 文本内容变化,节点复用
      const el = (n2.el = n1.el);
      if (n1.children !== n2.children) {
        // 更新文本
        hostSetText(el, n2.children);
      }
    }
  };

  const patchProps = (oldProps, newProps, el) => {
    for (let key in newProps) {
      hostPatchProp(el, key, oldProps[key], newProps[key]);
    }
    for (let key in oldProps) {
      if (!newProps[key]) {
        hostPatchProp(el, key, oldProps[key], undefined);
      }
    }
  };

  const unmountChildren = (children) => {
    for (let i = 0; i < children.length; i++) {
      unmount(children[i]);
    }
  };

  // 比较两个节点的差异
  const patchKeyChildren = (c1, c2, el) => {

  };

  // 比较两个节点的子节点,el为当前父节点
  const patchChildren = (n1, n2, el) => {
    const c1 = n1.children;
    const c2 = n2.children;
    const prevShapeFlag = n1.shapeFlag;
    const shapeFlag = n2.shapeFlag;

    // 新值为文本
    if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
      // 旧值为数组
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 文本 数组
        unmountChildren(c1);
      }
      if (c1 !== c2) {
        // 文本 文本
        hostSetElementText(el, c2);
      }
    } else {
      // 旧值为数组
      if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
        // 新值为数组
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          // 数组 数组 diff
          patchKeyChildren(c1, c2, el); // 全量更新,同级比较
        } else {
          // 空   数组
          unmountChildren(c1);
        }
      } else {
        if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
          // 空   文本
          // 数组 文本
          hostSetElementText(el, '');
        }
        if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
          // 数组 空
          // 数组 文本
          mountChildren(c2, el);
        }
      }
    }
  };

  // 先复用节点,然后比较属性,再比较子节点
  const patchElement = (n1, n2) => {
    // 复用节点
    let el = (n2.el = n1.el);
    let oldProps = n1.props || {};
    let newProps = n2.props || {};

    patchProps(oldProps, newProps, el);

    patchChildren(n1, n2, el);
  };

  const processElement = (n1, n2, container) => {
    if (n1 === null) {
      mountElement(n2, container);
    } else {
      // 对比元素
      patchElement(n1, n2);
    }
  };

  const patch = (n1, n2, container) => {
    if (n1 === n2) {
      return;
    }

    // 如果新值与老值完全没有可比性,删除老值,创建新值
    if (n1 && !isSameVNode(n1, n2)) {
      unmount(n1);
      n1 = null;
    }

    const { type, shapeFlag } = n2;

    switch (type) {
      case Text: // 文本
        processText(n1, n2, container);
        break;

      default:
        if (shapeFlag & ShapeFlags.ELEMENT) {
          // 元素
          processElement(n1, n2, container);
        }
        break;
    }
  };

  const unmount = (vnode) => {
    hostRemove(vnode.el);
  };

  const render = (vnode, container) => {
    if (vnode === null) {
      // 卸载dom
      if (container._vnode) {
        unmount(container._vnode);
      }
    } else {
      // 初始化及更新
      patch(container._vnode || null, vnode, container);
    }

    // 缓存下次直接更新
    container._vnode = vnode;
  };

  return { render };
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值