手写react练习-02

174 阅读3分钟

手写react练习-02

react原理练习将分以下几个流程:

流程一:(01的代码) 是了解虚拟dom的结构是怎么的。我们能否靠着虚拟dom实现渲染。**

流程二:了解 fiber,并懂得fiber 如何构建。 (该章节所处流程)

流程三:了解fiber的双缓存结构,setState的实现, useState的实现

流程四:expiretime解析、 react 的 diff、 ref的原理

fiber

为什么需要fiber

为什么需要fiber ?

对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验。

  1. 任务分解的意义,解决上面的问题
  2. 增量渲染(把渲染任务拆分成块,匀到多帧)
  3. 更新时能够暂停,终止,复用渲染任务
  4. 给不同类型的更新赋予优先级
  5. 并发方面新的基础能力
  6. 更流畅

1、window.requestIdleCallback()

方法将在浏览器的空闲时段内调用的函数排队。这使开发者能够在主事件循环上执行后台和低优先级工作,而不会影响延迟关键事件,如动画和输入响应。函数一般会按先进先调用的顺序执行,然而,如果回调函数指定了执行超时时间timeout,则有可能为了在超时前执行函数而打乱执行顺序。

了解这个api 后 实现思路是

window.requestIdleCallback() 调用一个wookloop的回调函数,在空闲时间执行渲染

wookloop 按照 构建好的fiber链表执行创建节点, 节点创建好了commit 挂载上dom

let nextUnitWork = null

// 用于记录render阶段Fiber树遍历过程中下一个需要执行的节点。

function wookLoop(idleDeadline) {
  while (nextUnitWork && idleDeadline.timeRemaining() > 1) {
    console.log('执行当前节点 并返回下一个节点')
    nextUnitWork = performNextUnitWork(nextUnitWork)
  }
  
  // 这里的工作是把生成好的节点挂到dom上
  if (!nextUnitWork && wipRoot) {
    commitRoot() // 提交根节点 开始挂载
  }
}

requestIdleCallback(wookLoop)

2、关于 performNextUnitWork

performNextUnitWork 的工作是 执行当前fiber(生成当前节点的dom),并返回下一个节点

function performNextUnitWork(workInProgress) {
  // todo
  const { type } = workInProgress

  if (isString(type)) {
    // 原生标签
    updateHostComponent(workInProgress)
  } else if (typeof type === 'function') {
    // 渲染函数
    type.prototype.isReactComponent
      ? updateClassComponent(workInProgress)
      : updateFunctionComponent(workInProgress)
  } else if (type === undefined) {
    // 渲染文本节点
    updateTextCompoent(workInProgress)
  } else if (typeof type === 'symbol') {
    // 渲染fragment
    updateFragement(workInProgress)
  }

  // 找子节点
  if (workInProgress.child) {
    return workInProgress.child
  }

  let nextFiber = workInProgress

  // 找邻节点, 没有就找父的领节点
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.return
  }

  return null
}

3、 commit 节点挂载


let wipRoot = null

function commitRoot() {
  commitWorker(wipRoot.child) 
  wipRoot = null
}

function commitWorker(workInProgress) {
  if (!workInProgress) {
    return
  }

  let parentNode = workInProgress.return

  while (parentNode) {
    if (parentNode.stateNode && !parentNode.stateNode.isReactComponent) {
      break
    }
    parentNode = parentNode.return
  }

  let parentDom = parentNode && parentNode.stateNode

  if (parentDom && workInProgress.stateNode) {
    if (!workInProgress.stateNode.isReactComponent) {
      parentDom.appendChild(workInProgress.stateNode)
    }
  }

  commitWorker(workInProgress.child)
  commitWorker(workInProgress.sibling)
}

4、 协调子节点

// 生成fiber节点
function reconcileChildren(workInProgress, children) {
  if (isString(children)) {
    return null
  }

  let newChildren = Array.isArray(children) ? children : [children]

  let lastBro = null
  for (let i = 0; i < newChildren.length; i++) {
    let child = newChildren[i]
    let newFiber = {
      type: child.type,
      props: isString(child) ? child : { ...child.props },
      stateNode: null,
      sibling: null,
      return: workInProgress,
    }

    if (i === 0) {
      workInProgress.child = newFiber
    }

    if (lastBro) {
      lastBro.sibling = newFiber
    }
    lastBro = newFiber
  }
}

5、 不同节点的渲染

function isString(sth) {
  return typeof sth === 'string'
}

// 根据vnode,生成node,只有原生标签才会走这个方法
function createNode(workInProgress) {
  let node = document.createElement(workInProgress.type)
  updateNode(node, workInProgress.props)
  return node
}
// 更新属性
function updateNode(node, nextVal) {
  Object.keys(nextVal).forEach((k) => {
    if (k === 'children') {
      if (isString(nextVal[k])) {
        node.textContent = nextVal[k]
      }
    } else {
      node[k] = nextVal[k]
    }
  })
}

function updateFragement(workInProgress) {
  let list = []
  reconcileChildren(workInProgress, workInProgress.props.children)
}

// 原生标签
function updateHostComponent(workInProgress) {
  if (!workInProgress.stateNode) {
    workInProgress.stateNode = createNode(workInProgress)
  }
  // children生成fiber
  reconcileChildren(workInProgress, workInProgress.props.children)
}

// 文本
function updateTextCompoent(workInProgress) {
  console.log(workInProgress)
  if (!workInProgress.stateNode) {
    workInProgress.stateNode = document.createTextNode(workInProgress.props)
  }
}

// 函数组件
function updateFunctionComponent(workInProgress) {
  const { type, props } = workInProgress

  const children = type(props)

  reconcileChildren(workInProgress, children)
}


// 类组件
function updateClassComponent(workInProgress) {
  const { type, props } = workInProgress
  const instance = new type(props)
  workInProgress.stateNode = instance
  const vvnode = instance.render()
  reconcileChildren(workInProgress, vvnode)
}

5、导出 render

function render(vnode, container) {
  console.log('vnode', vnode) //sy-log

  wipRoot = { // 设置根节点
    type: 'div',
    props: { children: vnode },
    stateNode: container,
  }

  nextUnitWork = wipRoot

  // node插入到container中
}