Hibop.github.io icon indicating copy to clipboard operation
Hibop.github.io copied to clipboard

关于Vue和React渲染相关

Open Hibop opened this issue 6 years ago • 0 comments

渲染器与协调器

渲染器: 将 Virtual DOM 渲染成特定平台下真实 DOM 的工具

渲染器函数: Render = (vDom, container) => Dom

分两个阶段:

  • mount: 初始化挂载init。 旧的 VNode 不存在,则直接将新的 VNode 挂载成全新的 DOM,这个过程叫做 mount。
  • path: update 如果旧的 VNode 存在,则会使用新的 VNode 与旧的 VNode 进行对比,试图以最小的资源开销完成 DOM 的更新,这个过程就叫 patch,或“打补丁”。 ==> 增量更新
if(!oldVNode && newVNode) {
  /* mount */
} else if(oldVNode && newVNode) {
  /* path */
} else if(oldVNode && !newVNode) {
  /* remove移除 */
}

渲染器的作用(责任)

  • 把 VNode 渲染成真实 DOM 的工具
  • 控制部分组件生命周期钩子的调用 在整个渲染周期中包含了大量的 DOM 操作、组件的挂载、卸载,控制着组件的生命周期钩子调用的时机。 其实一般应该有一个协调器的东西(react的Reconciliation翻译为“调和”)
  • 多端渲染的桥梁(抹平不同平台)
  • 异步渲染 Vue3 的异步渲染是基于调度器的实现,若要实现异步渲染,组件的挂载就不能同步进行,DOM的变更就要在合适的时机,一些需要在真实DOM存在之后才能执行的操作(如 ref)也应该在合适的时机进行。对于时机的控制是由调度器来完成的,但类似于组件的挂载与卸载以及操作 DOM 等行为的入队还是由渲染器来完成的,这也是为什么 Vue2 无法轻易实现异步渲染的原因。
  • 核心Diff 正是 Diff 算法的存在才使得 Virtual DOM如此成功

mount

function mount(vnode, container) {
  const { flags } = vnode
  if (flags & VNodeFlags.ELEMENT) {
    // 挂载普通标签
    mountElement(vnode, container)
  } else if (flags & VNodeFlags.COMPONENT) {
    // 挂载组件
    mountComponent(vnode, container)
  } else if (flags & VNodeFlags.TEXT) {
    // 挂载纯文本
    mountText(vnode, container)
  } else if (flags & VNodeFlags.FRAGMENT) {
    // 挂载 Fragment
    mountFragment(vnode, container)
  } else if (flags & VNodeFlags.PORTAL) {
    // 挂载 Portal
    mountPortal(vnode, container)
  }
}

patch ==> re-render

两步优化 类型: ['html/svg', 'Component', 'Text', 'Fragment', 'Portal']

  • 类型不同(flags): 直接替换(replace) 移除旧的挂载新的
  • 不同的标签渲染内容不同
  • 相同标签: 差异体现VNodeData和children上

VNodeData更新算法 patchData

  • 传递(新的VNodeData, 旧的VNodeData) 在mount阶段时'旧的VNodeData' = null;
  • 遍历新的 new VNodeData, 将新的数据挂载到元素上;
  • 遍历旧的 VNodeData,将已经不存在于新的 VNodeData 中的数据从元素上移除;
const domPropsRE = /\W|^(?:value|checked|selected|muted)$/;

export function patchData(el, key, prevValue, nextValue) {
  switch (key) {
    case 'style':
      // 将新的样式数据应用到元素
      for (let k in nextValue) {
        el.style[k] = nextValue[k]
      }
      // 移除已经不存在的样式
      for (let k in prevValue) {
        if (!nextValue.hasOwnProperty(k)) {
          el.style[k] = ''
        }
      }
      break
    case 'class':
      el.className = nextValue
      break
    default:
      if (key[0] === 'o' && key[1] === 'n') {
        // 事件
        el.addEventListener(key.slice(2), nextValue)
      } else if (domPropsRE.test(key)) {
        // 当作 DOM Prop 处理
        el[key] = nextValue
      } else {
        // 当作 Attr 处理
        el.setAttribute(key, nextValue)
      }
      break
  }
}

需要说明的是:更新属性包括四种

  • style
  • class
  • DOM property: 存在于DOM对象上的属性
  • Attribute: 那些存在于标签上的属性 当标签上存在非标准属性时,该属性不会被转化为 DOM Prop;

有些属性不能通过 setAttribute 设置,而是应该直接通过 DOM 元素设置:el.checked = true。好在这样的属性不多,我们可以列举出来:value、checked、selected、muted。除此之外还有一些属性也需要使用 Property 的方式设置到 DOM 元素上,例如 innerHTML 和 textContent 等等。

参考链接

  • http://hcysun.me/vue-design/zh/renderer-patch.html#%E6%9B%B4%E6%96%B0%E6%A0%87%E7%AD%BE%E5%85%83%E7%B4%A0%E7%9A%84%E5%9F%BA%E6%9C%AC%E5%8E%9F%E5%88%99
  • http://blog.codingplayboy.com/2017/12/02/react_fiber/
  • https://yuchengkai.cn/docs/frontend/framework.html#%E6%95%B0%E6%8D%AE%E5%8A%AB%E6%8C%81

Hibop avatar Jul 15 '19 11:07 Hibop