基于react16.8创建自己的react

基于这个网站实现的一个简易版react

https://pomb.us/build-your-own-react/

学完之后大概能懂得react的一个工作原理。
附代码如下:

const TEXT_ELEMENT = "TEXT_ELEMENT";

// 第一步 完成creaateElement
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        // 将普通元素比如字符串数字等封装成对象
        return typeof child === "object" ? child : createTextElement(child);
      }),
    },
  };
}

function createTextElement(text) {
  return {
    type: TEXT_ELEMENT,
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

// 我们将旧 Fiber 的 props 与新 Fiber 的 props 进行比较,移除掉掉的 props,设置新的或更改的 props。
const isProperty = (key) => key !== "children";
// 判断两者是否有变化,有变化才返回true
const isNew = (prev, next) => {
  return (key) => {
    return prev[key] !== next[key];
  };
};
// 判断key在不在新的props中,不在才返回true
const isGone = (prev, next) => {
  return (key) => {
    return !(key in next);
  };
};
// 处理事件
const isEvent = (key) => key.startsWith("on");
function updateDom(dom, prevProps, nextProps) {
  // 如若需要, 删除老的事件处理程序
  Object.keys(prevProps)
    .filter(isEvent)
    .filter((key) => !(key in nextProps) || isNew(prevProps, nextProps)(key)) //key不在新props或者是两个事件处理函数变化了
    .forEach((name) => {
      const eventType = name.toLowerCase().substring(2); // 获取事件类型
      dom.removeEventListener(eventType, prevProps[name]); //取消监听
    });
  // 删除老的节点
  Object.keys(prevProps)
    .filter(isProperty)
    .filter(isGone(prevProps, nextProps))
    .forEach((name) => (dom[name] = ""));

  // 设置或者改变节点
  Object.keys(nextProps)
    .filter(isProperty)
    .filter(isNew(prevProps, nextProps))
    .forEach((name) => (dom[name] = nextProps[name]));
  // 如若需要,添加新的事件处理程序
  Object.keys(nextProps)
    .filter(isEvent)
    .filter(isNew(prevProps, nextProps)) // 有变化了
    .forEach((name) => {
      const eventType = name.toLowerCase().substring(2);
      dom.addEventListener(eventType, nextProps[name]); //重新注册
    });
}

// 为每个fiber创建节点
function createDom(fiber) {
  const dom =
    fiber.type === TEXT_ELEMENT
      ? document.createTextNode("")
      : document.createElement(fiber.type);

  // 处理props,props.nodeValue会覆盖值
  updateDom(dom, {}, fiber.props);
  return dom;
}

// render创建一个root fiber,其余工作交给performUnitOfWork 给nextUnitOfWork赋值,准备开始工作
function render(element, container) {
  wipRoot = {
    // 第一个nextUnitOfWork,也可以算是root fiber
    dom: container,
    props: {
      children: [element], // 他的子节点就是render的react元素,如<div></div>
    },
    parent: null,
    child: null,
    sibling: null,
    alternate: currentRoot, //添加root fiber的alternamte
  };
  deletions = [];
  nextUnitOfWork = wipRoot;
}

let nextUnitOfWork = null; // 下一个工作单元
let currentRoot = null; // 指向当前的节点工作的fiber tree
let wipRoot = null; // 指向当前的节点工作的root fiber
let deletions = null;

// 开始调度
function workLoop(deadline) {
  let shouldYield = false;
  //当前是否应该渲染
  // 当还有需要工作的节点和不需要渲染的时候
  while (nextUnitOfWork && !shouldYield) {
    nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
    shouldYield = deadline.timeRemaining() < 1; //
  }

  // 一旦完成了所有的工作,那么就将整颗fiber树交给dom
  if (!nextUnitOfWork && wipRoot) {
    // commit阶段无法中断的
    commitRoot();
  }
  requestIdleCallback(workLoop); //浏览器会在主线程空闲的时候执行该回调。,react没有使用这个api,但这个案例,概念上是相通的。
  // requestIdleCallback执行的时候,传入了一个截止日期参数deadline。我们可以使用它来检查在浏览器需要再次控制之前我们还有多少时间。
}
requestIdleCallback(workLoop);

//1 将元素添加到 DOM
//2 为元素的子元素创建fiber
//3 选择下一个工作单元
function performUnitOfWork(fiber) {
  // type可能是类或者函数或者字符串
  const isFunctionComponent = fiber.type instanceof Function;
  if (isFunctionComponent) {
    // 处理组件的时候为fiber创建dom和为节点创建fiber。
    updateFunctionComponent(fiber); //处理函数组件
  } else {
    updateHostComponent(fiber); //处理原生组件
  }

  // 3 寻找下一个工作单元,遵循原则,大儿子,没大儿子就算完成,到下一个兄弟节点,没有兄弟节点的话,父级节点就算完成,到父级节点的兄弟节点,叔叔节点。
  if (fiber.child) {
    return fiber.child;
  }
  // 没有child,自己算完成了,到兄弟节点。
  let currentFiber = fiber;
  while (currentFiber) {
    const currentFiberSibling = currentFiber.sibling;
    if (currentFiberSibling) {
      //有兄弟节点
      return currentFiberSibling;
    }
    // 没有兄弟节点,父亲节点就算完成了,就要从父亲开始找叔叔节点了,如果没有叔叔节点,爷爷节点就算完成了,就要从爷爷开始找老叔叔节点了。依此类推。
    // 直到找到root fiber,他没有parent。
    currentFiber = currentFiber.parent;
  }
}

// 处理原生
function updateHostComponent(fiber) {
  if (!fiber.dom) {
    fiber.dom = createDom(fiber);
  }
  //需要为每个孩子创建一个新的fiber,并且将其加入fiber树
  const elements = fiber.props.children;
  reconcileChildren(fiber, elements); // 协调fiber和elements
}

// 在调用函数组件之前需要初始化一些全局变量,方便在useState中使用。
let wipFiber = null; //当前的函数组件
let hookIndex = null; // hooks索引

// 处理函数组件
function updateFunctionComponent(fiber) {
  wipFiber = fiber;
  hookIndex = 0;
  wipFiber.hooks = []; // 支持useState在同一个组件中多次引用,并且通过hookIndex跟踪当前的钩子索引。
  // 函数组件可以直接执行,获取return的vdom
  const children = [fiber.type(fiber.props)]; // App({name: 'xxx'})
  // 给返回的chilren创建fiber,调度children
  reconcileChildren(fiber, children);
}

// 函数组件调用useState的时候,检查是否有旧的钩子,用alternate使用hookIndex来检查fiber
function useState(initial) {
  // 是否第一次执行函数组件,如果是更新,那么可以通过wipFiber拿到老的状态,否则就用初始化的状态
  const oldHook =
    wipFiber.alternate &&
    wipFiber.alternate.hooks &&
    wipFiber.alternate.hooks[hookIndex];
  const hook = { state: oldHook ? oldHook.state : initial, queue: [] }; //每次的状态

  // 如果是更新阶段,那么第二次执行该工作单元的时候,就从存储的queue中取出函数执行,返回最新的状态给函数组件。
  const actions = oldHook ? oldHook.queue : [];
  actions.forEach((action) => {
    hook.state = action(hook.state);
  }); // 多个setState()的值是一样的,因为他们接受的参数都是同一个值。

  const setState = (action) => {
    hook.queue.push(action);
    // 新建一个root fiber节点,然后赋值给nextUnitOfWork,将一个新的正在进行的工作根设置为下一个工作单元,工作循环就可以开始一个新的渲染阶段。从头开始
    wipRoot = {
      dom: currentRoot.dom, //当前的fiber树
      props: currentRoot.props,
      alternate: currentRoot,
      parent: null,
      child: null,
      sibling: null,
    };
    nextUnitOfWork = wipRoot;
    deletions = [];
  };
  wipFiber.hooks.push(hook);
  hookIndex++; //指向最新的状态
  return [hook.state, setState];
}

// 调度子节点
// effecttage的类型
const UPDATE = "UPDATE"; // 更改
const PLACEMENT = "PLACEMENT"; //新增加
const DELETION = "DELETION"; //删除
// 在这里,我们将调和新fiber上一个节点旧fiber的子节点与新fiber的子节点。
// 迭代旧 Fiber ( wipFiber.alternate) 的子节点和我们想要协调的元素数组
function reconcileChildren(wipFiber, elements = []) {
  let index = 0;
  console.log("elements", elements);

  // oldFiber和element。这element是我们想要渲染到 DOM 的东西,oldFiber也是我们上次渲染的东西。
  let oldFiber = wipFiber.alternate && wipFiber.alternate.child; //老的fiber的大儿子节点, elements是新的fiber的子节点。
  let prevSibling = null; //指向上一个子节点,如果不是大儿子,他们新创建的子fiber,要作为上一个子fiber的兄弟节点,才能被加入fiber树。
  while (index < elements.length || oldFiber) {
    const element = elements[index]; // 当前的子节点
    let newFiber = null;

    // 比对element和oldFiber
    // react这里使用了key,可以更好的reconciler,比如检测元素数组中某个元素位置的改动。
    const sameType = oldFiber && element && element.type === oldFiber.type;
    if (sameType) {
      // 1 旧的fiber的child和当前fiber的大儿子节点一样类型 我们可以保留 DOM 节点并使用新的 props 更新它
      // 创建一个新的的fiber,保留旧fiber的dom和元素的props,并且为fiber添加一个新的属性,effectTag,这个在commit阶段会使用到。
      newFiber = {
        type: oldFiber.type,
        props: element.props, // 更新props
        dom: oldFiber.dom, //保留dom
        parent: wipFiber, //父节点指向新的fiber。
        alternate: oldFiber, //在这里给新的fiber加上alternate属性
        effectTag: UPDATE,
        child: null,
        sibling: null,
      };
    }

    if (element && !sameType) {
      // 2 类型不同并且有一个新元素,则意味着我们需要创建一个新的 DOM 节点 ,有可能是第一次渲染
      newFiber = {
        type: element.type,
        props: element.props,
        dom: null,
        parent: wipFiber,
        alternate: null, //新创建的fiber没有旧节点
        effectTag: PLACEMENT,
        child: null,
        sibling: null,
      };
    }

    if (oldFiber && !sameType) {
      //  类型不同并且有旧fiber,就得删除。,这里不需要新的fiber,需要的话也在第二个判断创建完毕了,所以将删除标签添加到旧节点。
      oldFiber.effectTag = DELETION;
      deletions.push(oldFiber); // 所以需要一个数组来跟踪删除的节点
    }

    // oldFiber要指向兄弟节点了,接着是兄弟节点的比较
    if (oldFiber) {
      oldFiber = oldFiber.sibling;
    }

    // 将新的fiber加入到fiber树中呢,将其设置为当前节点的子节点或者兄弟节点,主要取决于他是否是第一子节点。
    if (index === 0) {
      wipFiber.child = newFiber;
    } else {
      prevSibling.sibling = newFiber;
    }
    prevSibling = newFiber;
    index++;
  }
}

// 这个阶段一旦开始不能中断
function commitRoot() {
  deletions.forEach(commitWork); // 删除的节点需要先完成
  // 递归调用commitWork,无法中断
  commitWork(wipRoot.child);
  currentRoot = wipRoot;
  wipRoot = null;
}

function commitWork(fiber) {
  if (!fiber) {
    return;
  }
  // render阶段完毕,到了commit阶段。
  // 功能组件如函数组件。类组件的fiber没有dom
  let domParentFiber = fiber.parent;
  while (!domParentFiber.dom) {
    // 父fiber有dom就退出。
    domParentFiber = domParentFiber.parent;
  }

  const domParent = domParentFiber.dom;
  // 根据不同的effecttag来做不同的处理
  if (fiber.effectTag === PLACEMENT && fiber.dom !== null) {
    domParent.appendChild(fiber.dom);
  } else if (fiber.effectTag === DELETION) {
    // 删除,如果删除的是一个功能组件,那么要往儿子查找,因为功能组件没有dom
    if (fiber.dom) {
      domParent.removeChild(fiber.dom);
    } else {
      // 功能组件
      commitDeletion(fiber, domParent);
    }
  } else if (fiber.effectTag === UPDATE && fiber.dom !== null) {
    //更新dom的props
    updateDom(fiber.dom, fiber.alternate.props, fiber.props);
  }
  // 递归地将所有节点附加到 dom。
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

// 递归往下查找具有dom的fiber
function commitDeletion(fiber, domParent) {
  if (fiber.dom) {
    domParent.removeChild(fiber.dom);
  } else {
    commitDeletion(fiber.child, domParent);
  }
}

const React = {
  createElement,
  useState,
};
const ReactDOM = {
  render,
};

/** @jsx Didact.createElement */
function Counter() {
  const [state, setState] = React.useState(1);
  return React.createElement(
    "h1",
    { onClick: () => setState((c) => c + 1) },
    "Count:",
    state
  );
  // return (
  //   <h1 onClick={() => setState(c => c + 1)}>
  //     Count: {state}
  //   </h1>
  // )
}
const element = React.createElement(Counter, null);
const container = document.getElementById("root");
ReactDOM.render(element, container);

总结:

  • 每个vdom对应一个fiber

  • render的时候会创建一个root fiber,其余的工作由performUnitOfWork完成,

  • performUnitOfWork主要完成三件事情

    • 1 为当前的fiber节点创建dom
    • 2 为每一个子节点创建fiber。reconcilerChildren,并且将fiber组成树。
    • 3 选择下一个工作单元(顺序是儿子=》兄弟=〉叔叔节点)
  • react15的时候,更新是递归更新,无法中断,

  • react16主要实现了可中断的异步更新,工作架构采用了fiber架构,主要分为两个阶段,render阶段和commit阶段,render阶段,只有当浏览器有空余的时间,才会执行workloop函数去进行调度,当全部的fiber调度完毕之后,才会到达commit阶段。commitRoot方法是commit阶段工作的起点,commit阶段一旦开始无法中断,根据fiber的effectTag和effectList来进行更新。可以简单的理解为render阶段先找出所有的变化,然后commit阶段统一更新,找变化可以中断,更新无法中断。

  • useState的状态挂在了fiber上面,每次更新的时候可以通过alternate属性获取老的状态,还有actions,然后遍历执行actions,传入老的状态,去更新新的状态,由于遍历执行的acitons的入参都是老状态,所以多个setState的值还是一样的。如setState(c=>c+1);setState(c=>c+1);这个c永远是1,所以调用了两个也算是2。

以上仅是个人学习所得笔记,如有错误欢迎在评论区提出。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: React 16.8、17和18的变化如下: React 16.8引入了Hooks,这是一种新的API,可以让函数组件拥有类组件的功能,如状态管理和生命周期函数。Hooks的引入使得React的代码更加简洁,易于维护和测试。 React 17主要是一个稳定版本,没有引入太多新的功能。它主要是为了解决React版本升级时的问题,使得React的升级更加平滑和可预测。 React 18的变化还没有完全确定,但是它将会引入一些新的功能,如异步渲染、并发模式、服务器端渲染等。这些功能将会使得React更加强大和灵活,可以满足更多的应用场景。 ### 回答2: React是目前非常受欢迎的前端框架之一。随着时间的推移,React的版本不断升级。React 16.8、17和18是React的三个重要版本,每个版本都引入了新功能和改进。下面将详细解释这些版本的变化。 1. React 16.8 React 16.8发布于2019年2月6日,是一个重要版本,是React Hooks的第一个正式版本。Hooks是一种新功能,可以使开发人员更容易地编写可重用的组件。在React 16.8中,开发人员使用Hooks可以很容易地使用函数组件来实现状态管理、副作用等。Hooks提供了更多自由和灵活性,使开发人员能够更加轻松地编写组件,并减少了组件之间复杂的关联性。 2. React 17 React 17发布于2020年10月20日,是React的下一个主要版本。虽然React 17没有引入新的功能,但带来了一些重要的变化。其中最重要的是,它改变了React的事件处理机制,将事件处理程序委托给每个组件的祖先节点,从而实现了更好的封装性。这意味着,在React 17中,开发人员可以更加轻松地重构组件树而无需担心容易造成的性能问题。 此外,React 17取消了对IE11的支持(不过React 16仍然支持IE11),同时引入了新的React.StrictMode,以帮助开发人员在开发阶段发现潜在的问题。 3. React 18 React 18预计于2022年第一季度发布,目前还处于开发中,因此还没有正式发布。然而,React 18可能会引入一些很多人期望已久的功能,比如异步渲染和服务器端渲染的改进等。 总体而言,React 16.8、17和18都是非常重要的版本,它们在不同方面带来了不同的改进和功能。使用React的开发人员应该始终保持对这些版本的了解,以确保能够充分利用它们所提供的新功能和改进。 ### 回答3: React 16.8的变化: React 16.8React的一个重要版本,它引入了一些新的特性,最重要的是Hooks。Hooks是一种新的React功能,使开发人员能够在不编写类组件的情况下使用状态和其他React功能。 另一个重要的变化是React的性能提升。React 16.8具有更快的渲染速度和减少内存消耗的优化,这使得React在低端设备和网络情况下更加稳定和快速。 React 17的变化: React 17是React的重要更新,其中最大的变化是将渲染器从React核心中分离出来。这意味着必须单独安装渲染器包,但这也为渲染器提供了更多的独立性。 另一个重要的变化是引入新的生命周期方法。React 17添加了两个新的生命周期方法,分别是getDerivedStateFromProps()和componentWillReceiveProps()。这些方法允许开发人员在组件更新之前或组件更新之后做出更多的决策。 React 17还引入了一些改进的错误处理机制,使错误更容易被识别和调试。 React 18的变化: React 18是即将发布的版本,它将包含一些新的特性和改进。其中主要的变化包括更好的服务器渲染性能,更好的并发模式和更好的调试工具。 React 18还将包含一些对Hooks的改进,使hook更容易调试和优化性能。React开发人员还希望通过为应用程序提供即时刷新、代码分离和更好的资源管理来提高开发体验。 总的来说,React 16.8、17和18的变化都是为了提高React的性能、可用性和开发体验。这些变化为React开发人员提供了更多的工具和功能,使他们能够更好地构建高性能的React应用程序。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

coderlin_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值