当前位置:   article > 正文

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

vue 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
}

  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

后续可以使用 | 运算符进行多种类型存储,使用 & 运算符进行判断是否包含某一种类型( 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;
}

  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

虚拟节点不用考虑平台兼容,并且可以将虚拟节点利用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);
  }
}

  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

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 };
}

  • 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
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
  • 110
  • 111
  • 112
  • 113
  • 114
  • 115
  • 116
  • 117
  • 118
  • 119
  • 120
  • 121
  • 122
  • 123
  • 124
  • 125
  • 126
  • 127
  • 128
  • 129
  • 130
  • 131
  • 132
  • 133
  • 134
  • 135
  • 136
  • 137
  • 138
  • 139
  • 140
  • 141
  • 142
  • 143
  • 144
  • 145
  • 146
  • 147
  • 148
  • 149
  • 150
  • 151
  • 152
  • 153
  • 154
  • 155
  • 156
  • 157
  • 158
  • 159
  • 160
  • 161
  • 162
  • 163
  • 164
  • 165
  • 166
  • 167
  • 168
  • 169
  • 170
  • 171
  • 172
  • 173
  • 174
  • 175
  • 176
  • 177
  • 178
  • 179
  • 180
  • 181
  • 182
  • 183
  • 184
  • 185
  • 186
  • 187
  • 188
  • 189
  • 190
  • 191
  • 192
  • 193
  • 194
  • 195
  • 196
  • 197
  • 198
  • 199
  • 200
  • 201
  • 202
  • 203
  • 204
  • 205
声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/知新_RL/article/detail/746914
推荐阅读
相关标签
  

闽ICP备14008679号