vm._render 的实现 vm._render 最终是执行 createElement 返回 VNode 可以看出,vm._render 最终是执行 createElement 返回 vnode Virtual DOM 是 DOM 的一种抽象描述,是 js 对象,是生成几个关键属性:标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的…

vm._render 的实现

vm._render 最终是执行 createElement 返回 VNode

  1. initRender的定义
// vm.$createElement方法的定义就是在执行initRender方法的时候,
export function initRender(vm: Component) {
  vm._vnode = null; // the root of the child tree
  vm._staticTrees = null; // v-once cached trees
  const options = vm.$options;
  const parentVnode = (vm.$vnode = options._parentVnode); // the placeholder node in parent tree
  const renderContext = parentVnode && parentVnode.context;
  vm.$slots = resolveSlots(options._renderChildren, renderContext);
  vm.$scopedSlots = emptyObject;
  // bind the createElement fn to this instance
  // so that we get proper render context inside it.
  // args order: tag, data, children, normalizationType, alwaysNormalize
  // 模版编译生成的render,由template或者el生成的render
  vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false);
  // normalization is always applied for the public version, used in
  // 手写的render函数
  vm.$createElement = (a, b, c, d) => createElement(vm, a, b, c, d, true);

  // $attrs & $listeners are exposed for easier HOC creation.
  // they need to be reactive so that HOCs using them are always updated
  const parentData = parentVnode && parentVnode.data;

  /* istanbul ignore else */
  if (process.env.NODE_ENV !== 'production') {
    defineReactive(
      vm,
      '$attrs',
      (parentData && parentData.attrs) || emptyObject,
      () => {
        !isUpdatingChildComponent && warn(`$attrs is readonly.`, vm);
      },
      true
    );
    defineReactive(
      vm,
      '$listeners',
      options._parentListeners || emptyObject,
      () => {
        !isUpdatingChildComponent && warn(`$listeners is readonly.`, vm);
      },
      true
    );
  } else {
    defineReactive(vm, '$attrs', (parentData && parentData.attrs) || emptyObject, null, true);
    defineReactive(vm, '$listeners', options._parentListeners || emptyObject, null, true);
  }
}
  1. renderMixin的使用
// src/core/instance/render.js
export function renderMixin(Vue: Class<Component>) {
  // install runtime convenience helpers
  installRenderHelpers(Vue.prototype);

  Vue.prototype.$nextTick = function(fn: Function) {
    return nextTick(fn, this);
  };

  Vue.prototype._render = function(): VNode {
    const vm: Component = this;
    const { render, _parentVnode } = vm.$options;

    // reset _rendered flag on slots for duplicate slot check
    if (process.env.NODE_ENV !== 'production') {
      for (const key in vm.$slots) {
        // $flow-disable-line
        vm.$slots[key]._rendered = false;
      }
    }

    if (_parentVnode) {
      vm.$scopedSlots = _parentVnode.data.scopedSlots || emptyObject;
    }

    // set parent vnode. this allows render functions to have access
    // to the data on the placeholder node.
    vm.$vnode = _parentVnode;
    // render self
    let vnode;
    try {
      //
      vnode = render.call(vm._renderProxy, vm.$createElement);
    } catch (e) {
      handleError(e, vm, `render`);
      // return error render result,
      // or previous vnode to prevent render error causing blank component
      /* istanbul ignore else */
      if (process.env.NODE_ENV !== 'production') {
        if (vm.$options.renderError) {
          try {
            vnode = vm.$options.renderError.call(vm._renderProxy, vm.$createElement, e);
          } catch (e) {
            handleError(e, vm, `renderError`);
            vnode = vm._vnode;
          }
        } else {
          vnode = vm._vnode;
        }
      } else {
        vnode = vm._vnode;
      }
    }
    // return empty vnode in case the render function errored out
    if (!(vnode instanceof VNode)) {
      if (process.env.NODE_ENV !== 'production' && Array.isArray(vnode)) {
        warn('Multiple root nodes returned from render function. Render function ' + 'should return a single root node.', vm);
      }
      vnode = createEmptyVNode();
    }
    // set parent
    vnode.parent = _parentVnode;
    return vnode;
  };
}
  1. 将 template 转换成 render 函数
<div id="app">
  {{ message }}
</div>
render: function (createElement) {
  return createElement('div', {
     attrs: {
        id: 'app'
      },
  }, this.message)
}

可以看出,vm._render 最终是执行 createElement 返回 vnode

Virtual DOM

是 DOM 的一种抽象描述,是 js 对象,是生成几个关键属性:标签名、数据、子节点、键值等,其它属性都是都是用来扩展 VNode 的灵活性以及实现一些特殊 feature 的

createElement 的实现

  1. createElement的封装
// src/core/vdom/create-elemenet.js
export function createElement(context: Component, tag: any, data: any, children: any, normalizationType: any, alwaysNormalize: boolean): VNode | Array<VNode> {
  if (Array.isArray(data) || isPrimitive(data)) {
    normalizationType = children;
    children = data;
    data = undefined;
  }
  if (isTrue(alwaysNormalize)) {
    normalizationType = ALWAYS_NORMALIZE;
  }
  return _createElement(context, tag, data, children, normalizationType);
}
  1. _createElement的实现

    把 children 变成一维的 VNODE

// src/core/vdom/create-elemenet.js
// 参数1:context表示VNode上下文环境,是Component类型
// 参数2:tag表示标签,是一个字符串也可以是Component
// 参数3:data表示VNode数据,是VNodeData 类型
// 参数4:children表示VNode子节点,可以是任意类型
// 参数5:context表示子节点规范的类型,取决于是手写的render函数还是由template编译而成的
export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  // data不能是响应式的
  if (isDef(data) && isDef((data: any).__ob__)) {
    process.env.NODE_ENV !== 'production' && warn(
      `Avoid using observed data object as vnode data: ${JSON.stringify(data)}\n` +
      'Always create fresh vnode data objects in each render!',
      context
    )
    // 如果是响应式的,创建一个空VNode
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // teg存在,也会创建一个空节点
    return createEmptyVNode()
  }
  // 对key类型的处理与判断
  if (process.env.NODE_ENV !== 'production' &&
    isDef(data) && isDef(data.key) && !isPrimitive(data.key)
  ) {
    if (!__WEEX__ || !('@binding' in data.key)) {
      warn(
        'Avoid using non-primitive value as key, ' +
        'use string/number value instead.',
        context
      )
    }
  }
  // 对slot类型的处理
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  if (normalizationType === ALWAYS_NORMALIZE) {
    // 根据 normalizationType 的不同来决定调用normalizeChildren还是simpleNormalizeChildren
    // 1、如果是手写的render函数,是基础类型,当children只有一个节点的时候,vuejs允许把children写成基础类型来创建单个简单的文本节点,调用的是createTextVNode创建VNode
    // 2、如果是有嵌套,比如slot,v-for等,会调用 normalizeArrayChildren
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    // render是编译生成的,理论是已经都是VNode了,但是component返回的是一个数据,而不是根节点,所以要把数组展开
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  // 对 tag 做判断,如果是 string 类型
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    // 如果是html原生的保留标签
    if (config.isReservedTag(tag)) {
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
      // 如果是为已注册的组件名
    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      vnode = createComponent(Ctor, data, context, children, tag)
    } else {
      // unknown or unlisted namespaced elements
      // check at runtime because it may get assigned a namespace when its
      // 不认识的节点
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // 如果是 tag 一个 Component 类型
    vnode = createComponent(tag, data, context, children)
  }
  if (Array.isArray(vnode)) {
    return vnode
  } else if (isDef(vnode)) {
    if (isDef(ns)) applyNS(vnode, ns)
    if (isDef(data)) registerDeepBindings(data)
    return vnode
  } else {
    return createEmptyVNode()
  }
  1. simpleNormalizeChildren
// src/core/vdom/helpers/normalzie-children.js
// 展开操作
export function simpleNormalizeChildren(children: any) {
  for (let i = 0; i < children.length; i++) {
    if (Array.isArray(children[i])) {
      return Array.prototype.concat.apply([], children);
    }
  }
  return children;
}
  1. normalizeArrayChildren

递归、合并连续的 text 节点,目的把 children 编程一维的 VNode

// src/core/vdom/helpers/normalzie-children.js
// 参数1:children要规范的子节点
// 参数2:nestedIndex嵌套的索引
function normalizeArrayChildren(children: any, nestedIndex?: string): Array<VNode> {
  const res = [];
  let i, c, lastIndex, last;
  // 先进性遍历children
  for (i = 0; i < children.length; i++) {
    c = children[i];
    if (isUndef(c) || typeof c === 'boolean') continue;
    lastIndex = res.length - 1;
    last = res[lastIndex];
    //  如果是数组类型
    if (Array.isArray(c)) {
      if (c.length > 0) {
        // 递归调用
        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`);
        // 如果存在两个连续的 text节点,会把它们合并成一个 text 节点。
        if (isTextNode(c[0]) && isTextNode(last)) {
          res[lastIndex] = createTextVNode(last.text + (c[0]: any).text);
          c.shift();
        }
        res.push.apply(res, c);
      }
    } else if (isPrimitive(c)) {
      // 如果是基础类型则通过 createTextVNode 方法转换成 VNode 类型
      if (isTextNode(last)) {
        // 如果存在两个连续的 text节点,会把它们合并成一个 text 节点。
        res[lastIndex] = createTextVNode(last.text + c);
      } else if (c !== '') {
        // 生成一个text节点
        res.push(createTextVNode(c));
      }
    } else {
      if (isTextNode(c) && isTextNode(last)) {
        // 如果存在两个连续的 text节点,会把它们合并成一个 text 节点。
        res[lastIndex] = createTextVNode(last.text + c.text);
      } else {
        // children 是一个列表并且列表还存在嵌套的情况
        if (isTrue(children._isVList) && isDef(c.tag) && isUndef(c.key) && isDef(nestedIndex)) {
          // 则根据 nestedIndex 去更新它的 key
          c.key = `__vlist${nestedIndex}_${i}__`;
        }
        res.push(c);
      }
    }
  }
  return res;
}

暂无评论