首页 > 其他分享 >一周-每天1 小时,学习实现一个简单的mini-React

一周-每天1 小时,学习实现一个简单的mini-React

时间:2024-04-16 11:55:42浏览次数:28  
标签:function mini fiber dom 一周 props React child children

关于 从0到1实现一个 mini-React 过程

起因 --- 为什么会有这篇文章还得从前几天加入了一个十分"内卷"的群聊开始

崔学社 mini-React.偶然看到朋友圈一叫mini-React 的游戏副本课程,对这觉得这个课程听起来好像挺有意思的,大概看了一下课程内容,觉得很有意思,就果断加入了.然后就开始了一周的"内卷"生活.

过程 --- 下面我分享一下这一周"内卷"具体过程和内容, 课程目标是通过每天花1 小时左右,对照 React API 从 0-1,实现基本的功能. 根据我个人的划分难易程度,我会分成 --- 3篇文章来写;有兴趣的朋友可以访问我的github https://github.com/whoelse666/mini-React

---------- day01 ----------

当日目标:实现静态js dom 挂载渲染.

  1. 第一天就很基础的 js dom 操作,以及安装 vite 环境实用 jsx 不多讲,直接上代码,需要注意的是 main.js里面 script type='module'
    代码如下:
/* 
 main.js
*/

function createElement(type, props, children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child => {
        if (typeof child === 'string') {
          return createTextNode(child);
        } else {
          return child;
        }
      })
    }
  };
}

function createTextNode(nodeValue) {
  return {
    type: 'TEXT_ELEMENT',
    props: {
      nodeValue
      // children: []
    }
  };
}

function render(el, container) {
  const dom = el.type === 'TEXT_ELEMENT' ? document.createTextNode('') : document.createElement(el.type);
  Object.keys(el.props).forEach(key => {
    if (key !== 'children') {
      dom[key] = el.props[key];
    }
  });
  const children = el.props.children;
  children && children.forEach(child => render(child, dom));
  container.appendChild(dom);
}

const App = createElement('div', { id: 'app' }, ['hello ', 'world']);
const App1 = createTextNode('world');
const root = document.querySelector('#root');
render(App, root);


<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>day01</title>
</head>
<body>
  <div id="root"></div>
  <script type="module" src="main.js"></script>
</body>
</html>
  1. 安装vite 实用 jsx 通过以下命令创建/运行
npm init vite@latest
yarn create vite  或者  pnpm create vite

cd  project_name
pnpm  i
pnpm dev 

按照以下目录创建对应文件
App.jsx
main.jsx
core/React.js
core/ReactDom.js

image.png

---------- day02 ----------

当日目标: 实现任务调度器 和 fiber 架构.

利用了 requestIdleCallback API 实现任务调度,这个函数主要作用:函数将在浏览器空闲时期被调用 " ,防止 dom 很大情况下造成阻塞卡顿;对requestIdleCallback 不清楚的可以 到 查看文档: requestIdleCallback ". fiber这里依旧是模仿 React 使用链表结构将 vDom 进行处理;

代码如下:( index.html 和上面相同)

/* 
React.js
*/

/* 
vdom 结构
vdom: {
  type,
  props:{
  ...more,
  children:[]
  }
}
*/

function createElement(type, props, children) {
  return {
    type,
    props: {
      ...props,
      children: children.map(child => {
        if (typeof child === "string") {
          return createTextNode(child)
        } else {
          return child
        }
      })
    }
  }
}

function createTextNode(nodeValue) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue
      // children: []
    }
  }
}

 
requestIdleCallback(workLoop)

let nextWorkOfUnit = null

function workLoop(deadline) {
  let shouldYield = false
  const remainingTime = deadline.timeRemaining()
  while (!shouldYield && nextWorkOfUnit) {
    nextWorkOfUnit = performUnitOfWork(nextWorkOfUnit)
    shouldYield = remainingTime < 1
  }


  requestIdleCallback(workLoop)
}

//  vDom 转换成 真实的Dom node 节点
function createDom(type) {
  return type === "TEXT_ELEMENT" ? document.createTextNode("") : document.createElement(type)
}

// 设置 dom props 属性
function setDomProps(dom, props) {
  Object.keys(props).forEach(key => {
    if (key !== "children") {
      dom[key] = props[key]
    }
  })
}

function performUnitOfWork(fiber) {
  if (!fiber.dom) {
    //   todo 1. 创建 dom
    const dom = createDom(fiber.type)
    //  todo 2.处理 props
    setDomProps(dom, fiber.props)
    fiber.dom = dom
    fiber.parent.dom.append(dom)
  }

  // todo 3. 转换成链表
  const children = fiber.props.children
  let prevChild = null
  children &&
    children.forEach((child, index) => {
      if (index === 0) {
        // #root 下 第一个dom 是有 dom =( id='app'的 div) 的 dom特殊处理
        // 父子关系互相连接 parent ,child
        fiber.child = child
        child.parent = fiber
      } else {
        // 同一个 fiber 下的 child 满足都在同一级,所以用 sibling链接
        prevChild.sibling = child
        child.parent = prevChild.parent
      }
      prevChild = child
    })
  // todo 4. 处理当前任务后,返回下一个任务
  if (fiber.child) {
    return fiber.child
  }
  if (fiber.sibling) {
    return fiber.sibling
  }
  return fiber.parent?.sibling
}

function render(el, container) {
  nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el]
    }
  }
}
export default {
  render,
  createElement
}


 // ReactDom.js
import React from "./React.js";
const ReactDom = {
  createRoot(container) {
    return {
      render(rootDom) {
        React.render(rootDom, container);
      }
    };
  }
};

export default ReactDom;


//App.jsx

import React from './core/React.js';
const App = React.createElement("div", { id: "app" }, ["hello ", "world"]);

export default App;


//main.jsx

import React from "./core/React.js";
import ReactDOM from "./core/ReactDom.js";
import App from "./App.jsx";

ReactDOM.createRoot(document.getElementById("root")).render(App);

---------- day03 ----------

当日目标: 实现 function component 和 dom 挂载/更新的合并提交

在之前的代码中,频繁挂载 dom,引起页面的重绘,回流等问题,性能浪费,在今天
内容中主要是讲 Dom 结构整理完成后,统一一次性挂载到页面根节点;
然后是实现了对

function Counter(){return <div>666</div>}

这样的function 组件渲染的逻辑处理
代码如下:

/* 
React.js
*/

/* 
vdom 结构
vdom: {
  type,
  props:{
  ...more,
  children:[]
  }
}
*/
function createTextNode(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        const isTextNode =
          typeof child === "string" || typeof child === "number";
        return isTextNode ? createTextNode(child) : child;
      }),
    },
  };
}

function render(el, container) {
  nextWorkOfUnit = {
    dom: container,
    props: {
      children: [el],
    },
  };

  root = nextWorkOfUnit;
}

let root = null;
let nextWorkOfUnit = null;
function workLoop(deadline) {
  let shouldYield = false;
  while (!shouldYield && nextWorkOfUnit) {
    nextWorkOfUnit = performWorkOfUnit(nextWorkOfUnit);

    shouldYield = deadline.timeRemaining() < 1;
  }

  if (!nextWorkOfUnit && root) {
    commitRoot();
  }

  requestIdleCallback(workLoop);
}

function commitRoot() {
  commitWork(root.child);
  root = null;
}

function commitWork(fiber) {
  if (!fiber) return;

  let fiberParent = fiber.parent;
  while (!fiberParent.dom) {
    fiberParent = fiberParent.parent;
  }

  if (fiber.dom) {
    fiberParent.dom.append(fiber.dom);
  }
  commitWork(fiber.child);
  commitWork(fiber.sibling);
}

function createDom(type) {
  return type === "TEXT_ELEMENT"
    ? document.createTextNode("")
    : document.createElement(type);
}

function updateProps(dom, props) {
  Object.keys(props).forEach((key) => {
    if (key !== "children") {
      dom[key] = props[key];
    }
  });
}

function initChildren(fiber, children) {
  let prevChild = null;
  children.forEach((child, index) => {
    const newFiber = {
      type: child.type,
      props: child.props,
      child: null,
      parent: fiber,
      sibling: null,
      dom: null,
    };

    if (index === 0) {
      fiber.child = newFiber;
    } else {
      prevChild.sibling = newFiber;
    }
    prevChild = newFiber;
  });
}

function updateFunctionComponent(fiber) {
  const children = [fiber.type(fiber.props)];

  initChildren(fiber, children);
}

function updateHostComponent(fiber) {
  if (!fiber.dom) {
    const dom = (fiber.dom = createDom(fiber.type));

    updateProps(dom, fiber.props);
  }

  const children = fiber.props.children;
  initChildren(fiber, children);
}

function performWorkOfUnit(fiber) {
  const isFunctionComponent = typeof fiber.type === "function";

  if(isFunctionComponent){
    updateFunctionComponent(fiber)
  }else{
    updateHostComponent(fiber)
  }

  // 4. 返回下一个要执行的任务
  if (fiber.child) {
    return fiber.child;
  }

  let nextFiber = fiber;
  while (nextFiber) {
    if (nextFiber.sibling) return nextFiber.sibling;
    nextFiber = nextFiber.parent;
  }
}

requestIdleCallback(workLoop);

const React = {
  render,
  createElement,
};

export default React;



//App.jsx
import React from "./core/React.js";
function Counter({ num }) {
  return <div>count: {num}</div>;
}

function App() {
  return (
    <div>
      hi-mini-react
      <Counter num={10}></Counter>
      <Counter num={20}></Counter>
    </div>
  );
}


export default App;



//main.jsx

import React from "./core/React.js";
import ReactDOM from "./core/ReactDom.js";
import App from "./App.jsx";

ReactDOM.createRoot(document.getElementById("root")).render(App);

告一段落

以上内容是我个人觉得,相对比较简单的部分,使用过 React 开发的同学应该问题都不大.,后续我会继续完成后面部分的内容.觉得有用希望可以支持关注,当然也希望各位同学和大佬,可以多多提建议.

Tips 刚开始写这类文章,经验不足,如有不足希望可以留言指正

标签:function,mini,fiber,dom,一周,props,React,child,children
From: https://www.cnblogs.com/whoelse/p/18133398

相关文章

  • 重启React Native老项目的奇幻之旅:填坑实录与解决方案分享
    这两天为了重启五年前基于ReactNative(版本0.59.9)开发的老项目,经过各种填坑查询等操作,最终把它成功地运行起来了。在这篇文章中,我将详述那些遭遇的挑战以及对应的解决方案,以期为同样面临此类困境的开发者提供宝贵的经验参考。这个项目涉及到的环境基本版本信息如下:react:16.......
  • antd 的ProTable 的列加上ellipsis属性后,react-resizable会无法拖动列
    1、我尝试在列中加入ellipsis为true后,让内容超出后,用省略号表示。{title:displayName,dataIndex:propName,key:newPropName,width:widthIndex//,ellipsis:true}但是这引发了另外的问题。react-resizable配置的列可拖动无效了。额外的情况:obj.fixed='......
  • 5.CentOS-7-Minimal 安装KubernetesV1.23.17&DockerV20.10.23
    1.环境准备主节点IP:192.168.254.130node1IP:192.168.254.131node2IP:192.168.254.132OSversion:CentOS7miniCPUArchitecture:x86_64/amd64K8sversion:v1.23.17Dockerversion:20.10.232.安装前准备#安装依赖yuminstall-ycurlwgetsystemdbash-completi......
  • react native调试相关技巧
    ReactNative的Debug基础:https://reactnative.dev/docs/next/debugging   调出开发菜单DevMenu。cmd+D或Device->Shake   在DevMenu上可以选择“ShowElementInspector”,显示UI上的组件,但是这是直接在app上显示,不清楚,最好在DevTools上来查看元素。 ......
  • 如何使用React.js从头开始构建TODO应用
    如果你是React.js的新手,并且渴望投身应用程序开发,那么你来对地方了!跟着我一起通过这个教程,从头开始构建一个基本的TODO应用程序。(本文视频讲解:java567.com)TODO应用对初学者的重要性TODO应用作为初学者掌握新编程语言或框架基础知识的理想项目。它为学习基本概念提供了实际的上......
  • [转]写给前端的 react-native 入门指南
    前言本文主要介绍react-native(下称RN)的入门,和前端的异同点文章不涉及功能的具体实现选择优势我们先说说,为什么很多人会选择使用RN、他对应的特性和普通Web的区别前端资源,生态的互通因为使用的语言是JS和react,对于前端来说可以无缝切换,并且他还能......
  • Reactor 和 Proactor
    在socket编程中,Reactor和Proactor是两种常见的事件处理模式,它们用于处理异步I/O操作,但它们的工作方式略有不同。1.Reactor模式:-Reactor模式是一种基于事件驱动的设计模式,它通过一个事件循环来处理输入事件并分发给对应的事件处理器。-在Reactor模式中,有一个......
  • React.js 网站开发:实现滚动加载动画
    React.js网站开发:实现滚动加载动画极客前端探索者前沿技术的探索者,编码艺术的实践者 最近在开发官网的过程中,涉及到UI动画的制作,其中滚动效果的使用比较频繁,特此整理一下,以便查询和温习。平滑向上过渡动画这种往下滚动过渡渐变显示的动画是最常......
  • React笔记(一)
    基础型exportdefaultfunctionProfile(){return(<imgsrc="https://i.imgur.com/MK3eW3Am.jpg"alt="KatherineJohnson"/>)}默认 exportdefaultfunctionButton(){} importButtonfrom'./Button.js�......
  • React状态与引用(Refs)- 差异和使用场景
    在本文中,我们将深入比较React的state和refs,探讨它们在特定场景下的适用性。当需要在React应用程序中存储数据时,首先要考虑的问题是:“数据是否在组件的生命周期内的某个时刻发生变化?”如果不会,那么普通的const变量非常适合。然而,如果数据会发生变化,那么就需要使用useState和useR......