react 源码入门

214 阅读1分钟
1. 怎么学习react?

答:死磕; 下面是我学习的思路!能力不够,朋友来凑。。。我身边有个喜欢研究这个代码的朋友,看这里,基于他代码理解自己的react。

2. 最常见的就是说调度reconcile,这个怎么理解?

怎么实现重虚拟数据变成真实dom,(这个不做虚拟dom怎么生成的,其实就是babel解析jsx)。

  • 这需要先理解react 怎么调度,不是递归生成节点,它利用了浏览器给予的api requestIdleCallback,这个其实有浏览器兼容性问题,React 团队则是用 requestAnimationFrame 和 postMessage 模拟实现的,这里不做详细解释
  • 下面是看代码
function workLoop(deadline) {
  while (nextUnitOfWork && deadline.timeRemaining() > 1) {
    nextUnitOfWork = performUnitOfWork( )
    
  }
  // nextUnitOfWork undefined  执行这个时候是performUnitOfWork里面没有数据运行了
  if (!nextUnitOfWork && wipRoot) {
    console.log(nextUnitOfWork)
    console.log(wipRoot)
    // debugger
    commitRoot()
  }
  requestIdleCallback(workLoop) //解决递归一直循环完成才能出发问题
}

requestIdleCallback(workLoop) // 浏览器空闲的时候运行
  • 上面代码就是空闲时间获取虚拟demo 生成真实dom入口 performUnitOfWork
function updateDom (fiber,elements){
  let prevSibling = null
  let oldFiber = fiber?.alternate?.child;
  for(let i=0;i <elements.length;i++){
    const element = elements[i]
    const newFiber = {
      type: element.type,
      props: element.props,
      parent: fiber,
      alternate: oldFiber,
      dom: null,
      flag: FLAG_PLACEMENT,
    }
    const same = oldFiber && oldFiber.type === newFiber.type;
    // same type: update
    if (same) {
      newFiber.flag = FLAG_UPDATE;
      newFiber.dom = oldFiber.dom;
    }
     //这个地方的逻辑其实就是把自己的当前子元素赋值
     if (i == 0) {
      fiber.child = newFiber
    } else {
      prevSibling.sibling = newFiber
    }
    //这个地方利用了引用数据类型,其实给prevSibling赋值,newFiber下面也会出现sibling这个属性,这个地方赋值是为了performUnitOfWork 方法末端代码处理返回数据
     // refers oldFiber to next
     if (oldFiber) oldFiber = oldFiber.sibling;
    prevSibling = newFiber

}

function updateHostComponent(fiber) {
  f (!fiber.dom) {
    if (fiber.type === "TEXT_ELEMENT") {
      fiber.dom =   document.createTextNode(fiber.props.nodeValue)
    }else{
      fiber.dom = document.createElement(fiber.type);
    }
    updateNodeProps(fiber.dom, {}, fiber.props);
  }
  updateDom(fiber, fiber.props.children);
}

function updateFunctionComponent(fiber){
  currentHookFiber = fiber; //这个是让fiber拥有usestate 属性
  hookIndex = 0; //重置state状态
  fiber.hooks = []; 
  updateDom(fiber, [(fiber.type)(fiber.props)]);
}

function performUnitOfWork() { 
  let fiber = nextUnitOfWork
 // 这个地方判断类型是为了区分节点,比如是方法还是真实阶段,还是string标签
  if (typeof fiber.type === "string") {
    updateHostComponent(fiber);
  } else if (typeof fiber.type === "function") {
    updateFunctionComponent(fiber);
  } else {
    updateDom(fiber, fiber.props.children);
  }
  if (fiber.child) {
    return fiber.child
  }
  let nextFiber = fiber
  while (nextFiber) {
    if (nextFiber.sibling) {
      return nextFiber.sibling
    }
    nextFiber = nextFiber.parent
  }
}

如果上面的performUnitOfWork都处理好了,就会进入commitRoot这个方法


function getParentNode(fiber) {
//为了获取每一个真实父节点
  let parentFiber = fiber.parent;
  while (parentFiber) {
    if (parentFiber.dom) {
      return parentFiber.dom;
    }
    parentFiber = parentFiber.parent;
  }
  return null;
}

function commitRoot() {
  commitWork(wipRoot.child) //开始生成当前自己的节点
  currentRoot = wipRoot //这个地方赋值为了后面函数钩子使用
  wipRoot = null  //这个地方清空是为了不在进入commitRoot
}

function commitWork(fiber) {
  if (!fiber) {
    return
  }
  const { flag } = fiber;
  flag & FLAG_PLACEMENT &&  fiber.dom && parentNode.appendChild(fiber.dom) 
  flag & FLAG_UPDATE && updateNodeProps(
      fiber.dom,
      fiber.alternate.props,
      fiber.props
   );
  commitWork(fiber.child)//开始自己生成
  commitWork(fiber.sibling)//开始兄弟生成
}

以上代码生成就会生成正式节点,没有属性的节点,下面就开始绑定熟悉,事件updateNodeProps这个方法。


function updateNodeProps(node, oldProps, newProps) {
  if (!node?.setAttribute) return;
    for(let key in newProps){
      if (key === "children") {
        if (newProps[key].length ) {
          newProps[key].forEach((child,index) => {  
            if(child.type == 'TEXT_ELEMENT'){
              if(node&&node.firstChild&&node.firstChild.nodeValue){
                node.firstChild.nodeValue = child.props.nodeValue
                //这个就是更新文本,比较简单的方法(个人理解)
              }
            }
          })
        }
        continue;
      }
      if(key !== 'children'){
        if(key.startsWith("on")){
        //先删除事件在绑定事件
          if (oldProps[key]) {
            node.removeEventListener(
              key.substring(2).toLowerCase(),
              oldProps[key]
            );
          }
          node.addEventListener(key.substring(2).toLowerCase(), newProps[key]);
        }else{
        //添加元素
          node.setAttribute(key, newProps[key]);
        }
      }
    }
    for (let key in oldProps) {
      if (key === "children") continue;
      if (!(key in newProps)) {
        node.removeAttribute(key);
      }
    }
}

以上代码就是我理解的react调度理解的代码