• 首页 首页 icon
  • 工具库 工具库 icon
    • IP查询 IP查询 icon
  • 内容库 内容库 icon
    • 快讯库 快讯库 icon
    • 精品库 精品库 icon
    • 问答库 问答库 icon
  • 更多 更多 icon
    • 服务条款 服务条款 icon

VUE渲染subTree

武飞扬头像
ClyingDeng
帮助95

节点初始化

上文输出的subTree结果:

从中可以看出shapeFlag的值是9,代表我们的节点是元素也是文本。

我们进行subTree的初次渲染:

patch(null, subTree, container)

在patch中我们之前只写了组件的初始化,并没有考虑元素的情况,因此我们需要判断添加该条件情况:

// 传入的是元素
if (shapeFlag & ShapeFlags.ELEMENT) {
  processElement(n1, n2, container)
}

processElement中处理元素。

const processElement = (n1, n2, container) => {
  if (n1 == null) {
    // 元素初始化
    mountElement(n2, container)
  } else {
    // 元素更新
    patchElement(n2, container)
  }
}

如果一开始没有旧节点,就初始化元素;有,就进行对比更新。在此我们以mountElement初始化为例。

拿到我们的vnode,我们最先要做的就是创建真实元素(hostCreateElement),然后插入到相应的元素位置(hostInsert)。再将真实元素挂载到vnode上,方便后续使用。

接着我们处理vnode的children。mountElement接收的vnode的children可能是字符串、数组、对象数组、字符串数组。

如果孩子是文本,我们直接创建文本节点hostSetElementText(el, vnode.children as string)

const mountElement = (vnode, container) => {
  const { type, props, shapeFlag, children } = vnode
  let el = (vnode.el = hostCreateElement(vnode.type))
  // 处理孩子  
  if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 孩子是文本 创建文本
    hostSetElementText(el, vnode.children as string)
  } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 数组
    mountChildren(children, el)
  }
  hostInsert(el, container)
}

如果孩子是数组,我们就需要再次处理,走到mountChildren这个函数。

循环children,传入的数组可能不是vnode对象数组,我们就要将其统一格式化成对象数组,再进行初始化孩子节点。

const mountChildren = (children, container) => {
  // 如果是一个文本 直接el.textContent = children
  // 如果是数组 ['文本1','文本2']  就需要创建两个文本节点 塞入到元素中
  for (let i = 0; i < children.length; i  ) {
    // normalizeVNode  将字符串格式化成vnode对象
    const child = children[i] = normalizeVNode(children[i])
    patch(null, child, container) // 文本需要特殊处理
  }
}

其中normalizeVNode这个函数就是将字符串或数字组成的数组转成vnode对象数组。

export const Text = Symbol()
export function normalizeVNode(child) {
    if (typeof child === 'object') {
        // children arrays
        return child
    } else {
        // strings and numbers
        return createVNode(Text, null, String(child))
    }
}

我们这边考虑的比较简单,源码中判断的情况就比较全面了:

再次初始化孩子节点时,如果是文本情况,我们就需要特殊处理一下文本。

const patch = (n1, n2, container) => {
  if (n1 === n2) return
  const { type, shapeFlag } = n2
  switch (type) {
    case Text: // 文本
      processText(n1, n2, container)
      break
    default:
      // 传入的是元素
      if (shapeFlag & ShapeFlags.ELEMENT) {
        processElement(n1, n2, container)
      }
      // 组件
      else if (shapeFlag & ShapeFlags.COMPONENT) {
        processComponent(n1, n2, container)
      }
  }
}

参照源码,在此我也是用switch去判断,如果是文本情况就需要再去创建初始化文本--processText。

const processText = (n1, n2, container) => {
  if (n1 === null) {
    // 将n2初始化成文本,然后插入相应位置
    hostInsert((n2.el = hostCreateText(n2.children as string)),
      container,)
  }
  else {
    const el = (n2.el = n1.el)
    if (n2.children !== n1.children) { // 新孩子不等于旧孩子 直接替换
      hostSetText(el, n2.children as string)
    }
  }
}

和之前处理元素类似,如果一开始没有,就创建文本并将其插入到相应位置;如果两个节点都存在,我们用新的代替老的,创建新的文本节点。

当然不仅仅是文本需要特殊处理,还存在其他情况,比如说注释等。

元素孩子节点的初始化就此可以告一段落了,我们可以看下案例结果:

属性初始化

在元素初始化的时候,我们可以通过hostPatchProp进行属性初始化:

const mountElement = (vnode, container) => {
  // vnode中的children 可能是字符串 数组 对象数组  字符串数组
  const { type, props, shapeFlag, children } = vnode
  let el = (vnode.el = hostCreateElement(vnode.type))
  // 处理孩子  
  if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { // 孩子是文本 创建文本
    hostSetElementText(el, vnode.children as string)
  } else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) { // 数组
    mountChildren(children, el)
  }
   // 处理属性
   if (props) {
    for (const key in props) {
     hostPatchProp(el,key,null,props[key]) //给元素添加属性 
    }
   }
  hostInsert(el, container)
}

结果验证如下图:

可以看到点击文字时,成功触发add方法,并输出count自加后的结果。

这样我们的元素和属性就已经初始化完成了。

这篇好文章是转载于:学新通技术网

  • 版权申明: 本站部分内容来自互联网,仅供学习及演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,请提供相关证据及您的身份证明,我们将在收到邮件后48小时内删除。
  • 本站站名: 学新通技术网
  • 本文地址: /boutique/detail/tanfekbc
系列文章
更多 icon
同类精品
更多 icon
继续加载