第八章 挂载与更新

98 阅读2分钟

第八章 挂载与更新

一、挂载子节点和元素的属性

  1. 第一次 Vnode 渲染为 HTML,并且遍历递归渲染 Vnode 的子节点.
  2. 遍历 Vnode.props,添加为标签的属性
  3. 这里遍历是使用el.setAttribute(key, vnode.props[key])、但是它们有很多问题,
  4. 第一次渲染,或者是新和旧 Vnode 的类型不行等,并且是普通标签,就直接渲染

二、HTML Attibutes 与 DOM Properties

  • HTML 中 DOM 的 attibutes 与 properties 的关系
  1. attitudes 是只用 el.setAttribute() 来添加的属性
  2. properties 是只直接用 el 添加的属性、两者一般是一对一的,但是也有 attitudes 有的属性,properties 没有,也有反过来的、他们的映射关系不全,甚至有些时候他们的映射是一对多的
  3. HTML Attitudes 的作用是设置与之对应的 DOM Properties 的初始值。
<input value="aaa" />

当不输入修改 value 的情况下 console.log(el.getAttribute(‘value’))是 aaa, console.log(el[value])的值是 aaa 当修改了 input 中的值,el.value 随之改变,而 el.getAttribute(‘value’)依然是 aaa

三、class 处理

  1. vue 中 最终希望的结构 class 都为字符串,创建一个函数 normalizaeClass 自动转换为字符串,Vnode 的只要是 class 的值自动会调用这个函数,
  2. class 设置的方法,el.className 性能最优秀,所以做特别优化,之前的代码是使用 setAttibute,修改为如下优先判断 class 的情况
  3. 在 Vue 中除了 class 做了增强,style 也做了类似的增强

四、卸载操作

function render(vnode, container) {
  if (vnode) {
    // 新 vnode 存在,将其与旧 vnode 一起传递给 patch 函数进行打补丁
    patch(container._vnode, vnode, container);
  } else {
    if (container._vnode) {
      // 旧 vnode 存在,且新 vnode 不存在,说明是卸载(unmount)操作
      unmount(container._vnode);
    }
  }
  // 把 vnode 存储到 container._vnode 下,即后续渲染中的旧 vnode
  container._vnode = vnode;
}

function unmount(vnode) {
  const parent = vnode.el.parentNode;
  if (parent) {
    parent.removeChild(vnode.el);
  }
  // 调用生命周期函数
  // 执行各种逻辑
}

container 是一个 DOM 对象,他的_vnode 属性存储着,已经渲染的的子元素的 Vnode Vnode 一旦渲染,他的属性 el 就会存储,他对应的 DOM 对象, 当新的 Vnode 为 null,并且旧的 Vnode 存在,那么就会删除 container 下面的 DOM,也就是卸载

五、Fragment

Vue3支持组件是多个子节点,Vue2不支持,多种子节点就是一个Fragament类型,Fragament不会真的渲染,所以卸载只需要卸载子节点,比较也只需比较子节点 Fragment是没有处理属性的,因为本身不需要真实渲染

const newVnode = {
  type: 'div',
  children: [
    {
      type: Fragment,
      children: [
        { type: 'p', children: 'text 1' },
        { type: 'p', children: 'text 2' },
        { type: 'p', children: 'text 3' }
      ]
    },
    { type: 'section', children: '分割线' }
  ]
}

function unmount() {
    if (type === Fragment) { //新节点类型是 片段节点,并且新节点与旧节点类型一样
      if (!n1) { //旧节点不存在,直接变量children,创建
        n2.children.forEach(c => patch(null, c, container))
      } else {
        patchChildren(n1, n2, container)  //处理子节点,注意:这里片段是没有属性的
      }
      ...
    }
}