Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

createElement 函数做了什么? #14

Open
daodaolee opened this issue Feb 28, 2021 · 0 comments
Open

createElement 函数做了什么? #14

daodaolee opened this issue Feb 28, 2021 · 0 comments

Comments

@daodaolee
Copy link
Owner

上一篇讲到挂载是用 mountComponent 函数来进行的,而 mountCompnent 函数里面最主要的是一个updateComponent 函数执行:

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

这个函数在初次挂载和之后的更新都用到了,而 _render() 方法最终会调用 $option.render 函数, 函数主要的目的就是创建 VNode,除了 Virtual DOM 的定义之外,还有一个 createElement 函数,本篇就来说下 createElement 都做了什么事情,它在 src/core/vdom/create-element.js 中。

const SIMPLE_NORMALIZE = 1
const ALWAYS_NORMALIZE = 2

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

createElement 方法支持六个参数, context 其实就是vm实例, tagVNode 的标签,data 就是 VNode 的数据,children 就是一些子节点,后两个就是两个标识符,下面的:

if (Array.isArray(data) || isPrimitive(data)) {
  normalizationType = children
  children = data
  data = undefined
}

是对传入参数的一种重载,简单一点就是对于传入的参数不一致的话,会做一点处理,把所有的参数都往后移一个,然后把 data 改成 null。后面赋值一个 normalizationType,紧接着返回一个 _createElement 函数,然后把刚才的 normalizationType 放进去,也就是说 createElement 方法最终调用的是 _createElement,而 createElement 本身其实只对参数做了一层处理,而真正创建 VNode 的是 _createElement,那来看下这个函数的内容:

export function _createElement (
  context: Component,
  tag?: string | Class<Component> | Function | Object,
  data?: VNodeData,
  children?: any,
  normalizationType?: number
): VNode | Array<VNode> {
  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
    )
    return createEmptyVNode()
  }
  // object syntax in v-bind
  if (isDef(data) && isDef(data.is)) {
    tag = data.is
  }
  if (!tag) {
    // in case of component :is set to falsy value
    return createEmptyVNode()
  }
  // warn against non-primitive 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
      )
    }
  }
  // support single function children as default scoped slot
  if (Array.isArray(children) &&
    typeof children[0] === 'function'
  ) {
    data = data || {}
    data.scopedSlots = { default: children[0] }
    children.length = 0
  }
  if (normalizationType === ALWAYS_NORMALIZE) {
    children = normalizeChildren(children)
  } else if (normalizationType === SIMPLE_NORMALIZE) {
    children = simpleNormalizeChildren(children)
  }
  let vnode, ns
  if (typeof tag === 'string') {
    let Ctor
    ns = (context.$vnode && context.$vnode.ns) || config.getTagNamespace(tag)
    if (config.isReservedTag(tag)) {
      // platform built-in elements
      vnode = new VNode(
        config.parsePlatformTagName(tag), data, children,
        undefined, undefined, context
      )
    } else if (isDef(Ctor = resolveAsset(context.$options, 'components', tag))) {
      // component
      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
      // parent normalizes children
      vnode = new VNode(
        tag, data, children,
        undefined, undefined, context
      )
    }
  } else {
    // direct component options / constructor
    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()
  }
}

最上面:

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
  )
  return createEmptyVNode()
 }

它的意思是判断 data 是不是一个响应式的对象,如果 data__ob__ 属性,那么它就是一个响应式的,在开发环境中就会报错,然后返回创建一个空节点,就是说不允许 VNodedata 是响应式的。创建空 VNode 代码在 src/core/vdom/vnode.js 中:

export const createEmptyVNode = (text: string = '') => {
  const node = new VNode()
  node.text = text
  node.isComment = true
  return node
}

接着它判断了 data.is,如果不为 true,也返回一个空 VNode ,后面也是对 data 做了一层判定,继续又简单的判断了 children

到后面:

if (normalizationType === ALWAYS_NORMALIZE) {
  children = normalizeChildren(children)
} else if (normalizationType === SIMPLE_NORMALIZE) {
  children = simpleNormalizeChildren(children)
}

children 做了一层处理,首先 children 是一个数组,这个处理是判断 children 数组是单层嵌套还是多层,然后把它拍平,把 children 里的每一项都变成 VNode,代码如下:

// The template compiler attempts to minimize the need for normalization by
// statically analyzing the template at compile time.
//
// For plain HTML markup, normalization can be completely skipped because the
// generated render function is guaranteed to return Array<VNode>. There are
// two cases where extra normalization is needed:

// 1. When the children contains components - because a functional component
// may return an Array instead of a single root. In this case, just a simple
// normalization is needed - if any child is an Array, we flatten the whole
// thing with Array.prototype.concat. It is guaranteed to be only 1-level deep
// because functional components already normalize their own children.
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
}

// 2. When the children contains constructs that always generated nested Arrays,
// e.g. <template>, <slot>, v-for, or when the children is provided by user
// with hand-written render functions / JSX. In such cases a full normalization
// is needed to cater to all possible types of children values.
export function normalizeChildren (children: any): ?Array<VNode> {
  return isPrimitive(children)
    ? [createTextVNode(children)]
    : Array.isArray(children)
      ? normalizeArrayChildren(children)
    	: undefined
}

function normalizeArrayChildren (children: any, nestedIndex?: string): Array<VNode> {
  const res = []
  let i, c, lastIndex, last
  for (i = 0; i < children.length; i++) {
    c = children[i]
    if (isUndef(c) || typeof c === 'boolean') continue
    lastIndex = res.length - 1
    last = res[lastIndex]
    //  nested
    if (Array.isArray(c)) {
      if (c.length > 0) {
        c = normalizeArrayChildren(c, `${nestedIndex || ''}_${i}`)
        // merge adjacent text nodes
        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)) {
      if (isTextNode(last)) {
        // merge adjacent text nodes
        // this is necessary for SSR hydration because text nodes are
        // essentially merged when rendered to HTML strings
        res[lastIndex] = createTextVNode(last.text + c)
      } else if (c !== '') {
        // convert primitive to vnode
        res.push(createTextVNode(c))
      }
    } else {
      if (isTextNode(c) && isTextNode(last)) {
        // merge adjacent text nodes
        res[lastIndex] = createTextVNode(last.text + c.text)
      } else {
        // default key for nested array children (likely generated by v-for)
        if (isTrue(children._isVList) &&
          isDef(c.tag) &&
          isUndef(c.key) &&
          isDef(nestedIndex)) {
          c.key = `__vlist${nestedIndex}_${i}__`
        }
        res.push(c)
      }
    }
  }
  return res
}

以上就是 _createElement 做的第一件事情:拍平 children,第二件事就是创建 vnode,判定分别是:html元素,组件,或者无法识别的节点。最终 vnode 就是 render.js 里面 vnode = render.call(vm._renderProxy, vm.$createElement) 的第二个参数,而 render.call 的结果就是 vm._c = (a, b, c, d) => createElement(vm, a, b, c, d, false) 中的 createElement 的返回值,所以 vnode 最终就是 createElement 的结果,接着 createElementvm._render 的返回值,再回到之前的 updateComponent

updateComponent = () => {
  vm._update(vm._render(), hydrating)
}

vm._update 的第一个参数就是刚才的 createElement 的返回值,到此,拿到了一个完整的 vnode ,下篇说一下 _update 是将 vnode 如何变成真实的 DOM 节点。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant