框架操作的是啥?——虚拟DOM

虚拟DOM 的作用是啥?

虚拟DOM 就是用 JS 对象表示真实DOM

性能

先在虚拟DOM 上进行一次 diff 算法,就不用在真实 DOM 上操作本不需要更新的东西

跨平台

虚拟DOM 意味着 他不是真正的DOM 这句话听起来有点废话
总之就是 他有利于跨平台开发

哪来的虚拟DOM?—— dsl 编译

dsl 是 domain specific language ,领域特定语言

html、css、都能算是 web dsl

像前面的 vdom,虽然经常听到,但是我们肯定不会说自己手写一个JS对象当作 vdom 的

vdom 就是虚拟DOM

像 react 就是写 jsx 编译为 vdom
其实上一句话不太准确,应该是 编译为 一个 渲染函数 render function 然后执行结果返回一个 vdom

所以平时我们写的 非常接近 html 的 jsx

vue 的 模板 也差不多把 差别:

  • vue template compiler 自家团队写的
  • react jsx 靠 babel

react 里 Babel 就会将 jsx 编译为 React.createElement(...) 然后这个函数执行结果 就是 vdom

谁来把 虚拟DOM 渲染 真实 DOM

到最后 肯定还是要回到 DOM API 来进行修改、把最终 DOM 展现出来

  • document.createElement 创建元素
  • setAttribute 设置属性
  • addEventListener 设置事件监听器

然后 渲染 也要根据 哪里发生了什么变化来进行渲染
这些修改标志 都是有 tag 的,具体操作也就是根据 传入的 tag 来进行对应的操作 —— 也就是最平凡 的 switch case …

  • HostComponent 就是元素
  • HostText 就是文本
  • FunctionComponent 函数组件
  • ClassComponent 类组件

    组件怎么渲染?

    其实就是调用对应的 函数、class形式的 render 函数
    也就是说 组件实际上就是 vdom 不同形式的封装

状态管理

react 通过 setState api 触发状态更新,更新之后重新渲染

为什么 vue 可以做到精准地更新变化的组件

vue 的状态管理是 响应式的
不论是哪里的组件,只要用到了对应的状态就要作为依赖存储起来,状态变化就触发相应的render

而 react 某个组件的更新 也有可能触发其他位置的更新,但是 react 不知道,所以他就要 更新全部

这应该 是 框架之间最大的区别了

react 架构

react 15

  • Reconciler(协调器)—— 负责找出变化的组件
    • 调用函数组件、或class组件的render方法,将返回的JSX转化为虚拟DOM
    • 将虚拟DOM和上次更新时的虚拟DOM对比
    • 通过对比找出本次更新中变化的虚拟DOM
    • 通知Renderer将变化的虚拟DOM渲染到页面上
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上

协调器和渲染器是交替工作的!
它是递归执行的,层级很深时,JS执行时间就超过了 16ms ,交互就会卡顿

JS 执行 和 GUI 渲染是不能同时的,也就是说 JS 执行会阻塞 渲染

15 缺点以及改进方向

因为 react 状态管理的缘故, react 修改了东西,必然是要渲染全部 vdom,计算的总量是没机会缩减的
但是又想要不阻塞加载,那要怎么样?
那干脆将全部的工作量分散到每个 16ms 中
也就是将计算分多次操作
但是直接打断,也不行,react 15 架构是无法支持 异步更新的。
直接打断意味着 DOM 渲染可能是不完全的,因为协调器和渲染器是交替工作的


react 16 fiber 架构

  • Scheduler(调度器)—— 调度任务的优先级,高优任务优先进入Reconciler
  • Reconciler(协调器)—— 负责找出变化的组件
    • 从原来的 递归渲染改为 循环 workLoop()渲染
      • 每次循环中都会调用 一个 shouldYield方法来判断当前是否有剩余时间
  • Renderer(渲染器)—— 负责将变化的组件渲染到页面上

调度器和协调器

react 16 架构 ,协调器和渲染器不是交替进行的。
当调度器将任务交给 协调器,协调器会为发生变化的 vdom 打上相应的 tag
调度器和协调器都是在 内存中执行,
是可以被打断的:

  • 有更高优先级的任务
  • 当前帧没有剩余时间了

因为没有真正触碰到 DOM,所以中断后不会导致 用户看到更新不完全的 DOM

只有当全部组件都搞完了,才会统一给到 渲染器

fiber 链表结构

fiber 即是上面那种 渲染流程,也指这个链表结构

打断后,还要想办法得到父节点和兄弟节点啊
所以一个节点具有这几个指针

  • childre 记录子节点
  • sibling 记录 兄弟节点
  • return 记录 父节点

并且如果发生了更改,还会加上标记,然后将该节点放进 effectList

渲染器执行三小段

渲染器执行的时候,也就是 commit 阶段 ,其中又分为三小段:

  1. before mutation
    1. 异步调度 useEffect 的时候
  2. mutation
    1. 这里就是遍历 effectList 来更新 DOM
  3. layout

    1. useLayoutEffect 的时候 —— 要拿到布局信息,肯定要等 DOM 操作完的时候

    这两个钩子都是 异步的,不然在 操作 DOM 那里阻塞的话,这架构优化不就白搞了吗