🤔手写React,探寻React设计思路

42 阅读3分钟

JSX 转换 虚拟 DOM

JSX(JavaScript XML) ,在 JS中写 HTML

  • JSX中 HTML 本就不是真实的 DOM
  • 通过 Babel 转换为 createElement
  • createMelment 返回树状对象数据

image.png

code.png

这段树状对象数据就是虚拟 DOM

优势

  • 跨平台

虚拟 DOM 是一个纯 JavaScript 对象

  • 性能优化

频繁的 DOM 操作会导致性能瓶颈,而虚拟 DOM 可以将多次操作合并为一次,减少了重排和重绘的次数

递归渲染虚拟 DOM 树阻塞单线程

虚拟 DOM 中存在三部分type(元素类型),Property(属性),Children(子元素)

code.png

缺点

长时间阻塞主线程,影响浏览器更优先的事件

利用空闲时间渲染

image.png React16递归的无法中断的更新重构为异步的可中断更新,但用与递归的数据结构无法满足条件(记录当前节点与下一个渲染节点),于是,Fiber结构应相应出来了

Fiber

通过 child ,parent ,sibling记录当前节点与下一个渲染节点,更好的分块渲染

image.png Fiber 结构

code.png

生成 Fiber

  1. 把元素添加到 dom 中
  2. 为元素的子元素都创建一个 fiber 结构
  3. 找到下一个工作单元 code.png

空闲时间渲染 Fiber

未命名.png

Render分割为 Render 和 Commit 阶段

上面的 createFiber 里 ,把元素直接添加到 dom 上,因为浏览器随时都有可能中断我们的操作,这样呈现给用户的就是一个不完整的 UI

需要等所有工作单元执行完后,我们再一并进行 所有 dom的添加

未命名.png

Diff 算法

Diff 算法特点

  1. 分层,同级比较:React 将整个 DOM 树分为多个层级,然后逐层比较,只比较同一层级的节点,从而减少比较的复杂度。同级比较时按照从左到右的顺序进行比较。
  2. 元素类型对比: 两个不同类型的元素会生成不同的树,如果元素类型发生了变化,React 会销毁旧树并创建新树。
  3. key 属性:React 使用 key 属性来标识节点的唯一性,从而在比较时能够快速定位到需要更新的节点。

未命名.png 更新 DOM

未命名.png 函数式组件

function App(props) {
  return <h1>Hi {props.name}</h1>
}
const element = <App name="foo" />
const container = document.getElementById("root")
Didact.render(element, container)

函数式组件有两点不同,如下:

  1. 函数式组件没有 dom 节点 ?
  2. 他的 children 属性 不在 props 上,而是 他的返回值

未命名.png

Hooks

给函数式组件增加 状态(state)

// 保存当前的 fiber
let wipFiber = null
// 保存当前执行 hook 的索引,区分每次执行是哪个 hook 
let hookIndex = nullfunction updateFunctionComponent(fiber) {
  wipFiber = fiber
  hookIndex = 0
  wipFiber.hooks = []
  const children = [fiber.type(fiber.props)]
  reconcileChildren(fiber, children)
}

实现 useState 钩子


function useState(initial) {
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  const hook = {
    // 存在旧值,则直接取,否则取传入的初始值
    state: oldHook ? oldHook.state : initial,
    // 存放 每次更新 状态的队列
    queue: []
  };

  const actions = oldHook ? oldHook.queue : [];
  actions.forEach(action => {
    hook.state = action(hook.state);
  });

  const setState = action => {
    hook.queue.push(action);
    wipRoot = {
      dom: currentRoot.dom,
      props: currentRoot.props,
      alternate: currentRoot
    };
    // 设置为下一个 工作单元,这样就可以开启一个新的 渲染
    nextUnitOfWork = wipRoot;
    deletions = [];
  };

  wipFiber.hooks.push(hook);
  hookIndex++;
  return [hook.state, setState];
}