1、Fiebr 作为数据结构

每个Fiber节点有个对应的React element,多个Fiber节点是如何连接形成树呢?

例如我们有这样一个属性结构:

  1. react.render(
  2. <div>
  3. <h1>
  4. <p />
  5. <a />
  6. </h1>
  7. <h2 />
  8. </div>,
  9. root
  10. )

React Fiber 机制的实现,是依赖下面这种数据结构(链表),每一个节点都是一个fiber。一个 fiber 包括了 child(第一个子节点)、sibling(兄弟节点)、parent(父节点)属性。

  1. // 指向父级Fiber节点
  2. this.return = null;
  3. // 指向子Fiber节点
  4. this.child = null;
  5. // 指向右边第一个兄弟Fiber节点
  6. this.sibling = null;

image.png

2、构建过程

在构建中,我们将创建根fiber并将其设置为 nextUnitOfWork。剩下的工作将在 perforformunitofwork 功能上进行,每个fiber做了三件事:

  • 将元素添加到 DOM
  • 为元素的子元素创建fiber
  • 选择下一个工作单元

实现目的是为了查找下一个工作单元变得更容易,使用的深度优先遍历,先查找子节点,在查找兄弟节点。

上图渲染过程详细描述如下:

  1. 当完成root的fiber工作时,如果有孩子,那么fiber是下一个工作的单元,root的子节点是div
  2. 当完成div的fiber工作时,下一个工作单元是h1
  3. h1的节点是p,继续下一个工作单元p
  4. p没有子节点,去找兄弟节点a
  5. a兄弟节点和子节点都没有,返回到父亲节点h1
  6. h1的子节点都已经工作完成了,去找h1的兄弟节点h2
  7. h2既没有兄弟节点,也没有子节点,返回到父亲节点div
  8. 同上,div在返回到父亲节点root
  9. 至此已经完成了所有的渲染工作

3、代码实现

1.抽离DOM节点的代码,放入到createDom()函数中;

  1. /**
  2. * 创建DOM
  3. * @param {*} fiber fiber节点
  4. */
  5. function createDom(fiber) {
  6. const dom =
  7. fiber.type == "TEXT_ELEMENT"
  8. ? document.createTextNode("")
  9. : document.createElement(fiber.type)
  10. const isProperty = key => key !== "children"
  11. Object.keys(fiber.props)
  12. .filter(isProperty)
  13. .forEach(name => {
  14. dom[name] = fiber.props[name]
  15. })
  16. return dom
  17. }

2.在 render 函数中,设置nextUnitOfWork为fiber的根节点,根节点只包含一个children属性;

  1. export function render(element, container) {
  2. // 将根节点设置为第一个将要工作单元
  3. nextUnitOfWork = {
  4. dom: container,
  5. props: {
  6. children: [element],
  7. },
  8. }
  9. }

3.当浏览器存在空闲时间,开始处理根节点;

  1. /**
  2. * 处理工作单元,返回下一个单元事件
  3. * @param {*} nextUnitOfWork
  4. */
  5. function performUnitOfWork(fiber) {
  6. // 如果fiber上没有dom节点,为其创建一个
  7. if (!fiber.dom) {
  8. fiber.dom = createDom(fiber)
  9. }
  10. // 如果fiber有父节点,将fiber.dom添加到父节点
  11. if (fiber.parent) {
  12. fiber.parent.dom.appendChild(fiber.dom)
  13. }
  14. }

3.为每一个孩子节点创建一个新的fiber;

  1. function performUnitOfWork(fiber) {
  2. // 省略上面内容
  3. // 获取到当前fiber的孩子节点
  4. const elements = fiber.props.children
  5. // 索引
  6. let index = 0
  7. // 上一个兄弟节点
  8. let prevSibling = null
  9. // 遍历孩子节点
  10. while (index < elements.length) {
  11. const element = elements[index]
  12. // 创建fiber
  13. const newFiber = {
  14. type: element.type,
  15. props: element.props,
  16. parent: fiber,
  17. dom: null,
  18. }
  19. }
  20. }

4.将新节点添加到fiber树中;

  1. function performUnitOfWork(fiber) {
  2. // 省略上面内容
  3. while(index < elements.length){
  4. // 省略上面内容
  5. // 将第一个孩子节点设置为 fiber 的子节点
  6. if (index === 0) {
  7. fiber.child = newFiber
  8. } else if(element) {
  9. // 第一个之外的子节点设置为第一个子节点的兄弟节点
  10. prevSibling.sibling = newFiber
  11. }
  12. prevSibling = newFiber
  13. index++
  14. }
  15. }

5.寻找下一个工作单元,先查找孩子,然后兄弟,如果没有就返回父节点;

  1. function performUnitOfWork(fiber) {
  2. // 省略上面内容
  3. // 寻找下一个孩子节点,如果有返回
  4. if (fiber.child) {
  5. return fiber.child
  6. }
  7. let nextFiber = fiber
  8. while (nextFiber) {
  9. // 如果有兄弟节点,返回兄弟节点
  10. if (nextFiber.sibling) {
  11. return nextFiber.sibling
  12. }
  13. // 否则返回父节点
  14. nextFiber = nextFiber.parent
  15. }
  16. }

4、效果

修改src/index.js文件
image.png

实现效果:
image.png

5、本节代码

代码地址:https://github.com/linhexs/mini-react/tree/4.fiber