当前位置: 首页 > article >正文

不只是mini-react第一节:实现最简单mini-react

项目总结构:
├─ 📁core
│  ├─ 📄React.js
│  └─ 📄ReactDom.js
├─ 📁node_modules
├─ 📁tests
│  └─ 📄createElement.spec.js
├─ 📄App.js
├─ 📄index.html
├─ 📄main.js
├─ 📄package-lock.json
├─ 📄package.json
└─ 📄pnpm-lock.yaml
原生js怎么写?
const dom = document.createElement("div")
dom.id="app"
document.querySelector('#root').append(dom)

const textNode = document.createTextNode("")
textNode.nodeValue = "app"
dom.append(textNode)
为什么需要虚拟dom?

通过以上原生写法可以看出,虚拟dom可以简化开发,使代码更加框架、结构化,清晰可读易于维护。

框架层面:频繁的dom操作会一直导致浏览器重排和重绘,会严重影响性能。

原生层面:相对于原生层面虚拟dom对性能提示微乎其微,并且在复杂情况会损耗性能。

当然虚拟dom还有个非常重要的作用就是跨端运行。

面试怎么答?

  • 减少浏览器重排和重绘(框架层面、原生层面)
  • 跨平台运行,不局限于浏览器
  • 减少心智负担,提高开发效率

当然不能干巴巴的把这几点甩出去,记得拓展。

极简版React内核

此处代码实现了一个极简的创建虚拟dom和虚拟dom转真实dom

/core/React.js

//创建文本对象虚拟dom
function createTextNode(text) {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue: text,
      children: [],
    },
  };
}

//创建虚拟dom对象
function createElement(type, props, ...children) {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        return typeof child === "string" ? createTextNode(child) : child;
      }),
    },
  };
}

//渲染器,vdom->tdom
function render(el, container) {
  const dom =
    el.type === "TEXT_ELEMENT"
      ? document.createTextNode("")
      : document.createElement(el.type);

  // id class
  Object.keys(el.props).forEach((key) => {
    if (key !== "children") {
      dom[key] = el.props[key];
    }
  });

  const children = el.props.children;
  children.forEach((child) => {
    render(child, dom);
  });

  container.append(dom);
}

const React = {
  render,
  createElement,
};

export default React;

可以看到以上代码中createElement函数就实现了一个极简版的虚拟dom,里面有三个元素分别是:

  • type:类型
  • props:传入的值
  • children:子节点

render函数则实现了一个极简版的渲染器,用于将虚拟dom转化成真实dom,传入的值分别是:

  • el:虚拟dom对象
  • container:具体在哪个真实dom节点渲染
创建虚拟dom对象

/App.js

通过上面定义的createElement函数来创建一个虚拟dom对象app并将其导出

import React from './core/React.js';
const App = React.createElement("div", { id: "app" }, "hi- ", "mini-react");
export default App
极简React渲染组件

定义了一个createRoot函数,通过传入的渲染容器container和虚拟dom对象App来将虚拟dom元素渲染到真实dom上。

/core/ReactDom.js

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

export default ReactDOM;
创建ReactDOM

/main.js

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

ReactDOM.createRoot(document.querySelector("#root")).render(App);
使用vitest单元测试验证

控制台运行npm i vitest下载依赖。

然后再package.json中自定义测试脚本

/package.json

{
  "scripts": {
    "test": "vitest"
  },
  "devDependencies": {
    "vitest": "^1.1.3"
  }
}

然后创建tests文件夹并编写测试文件,具体路径于代码如下:

/tests/createElement.spec.js

import React from "../core/React";
import { it, expect, describe } from "vitest";

describe("createElement", () => {
  it("props is null", () => {
    const el = React.createElement("div", null, "hi");

    expect(el).toMatchInlineSnapshot(`
      {
        "props": {
          "children": [
            {
              "props": {
                "children": [],
                "nodeValue": "hi",
              },
              "type": "TEXT_ELEMENT",
            },
          ],
        },
        "type": "div",
      }
    `)

  });

  it("should return element vdom", () => {
    const el = React.createElement("div", {id:"id"}, "hi");

    expect(el).toMatchInlineSnapshot(`
      {
        "props": {
          "children": [
            {
              "props": {
                "children": [],
                "nodeValue": "hi",
              },
              "type": "TEXT_ELEMENT",
            },
          ],
          "id": "id",
        },
        "type": "div",
      }
    `)

  });
});

创建完成后在控制台输入npm test即可运行测试脚本。

在上面代码中,describe用于创建测试组,每个it是测试组内的单个测试单元,而it内就是具体测试内容。

这里使用的是toMatchInlineSnapshot快照测试,用于比对el结果是否与快照函数toMatchInlineSnapshot内容一致,如果一致则测试通过。

总结

createRoot用于将虚拟dom渲染成真实dom

createElement用于创建虚拟dom对象

rendercreateRoot的内核

贴上main.js

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>

<body>

  <div id="root"></div>
  <script type="module" src="main.js"></script>
</body>

</html>

http://www.kler.cn/a/468110.html

相关文章:

  • Telephony Netd
  • 提升自闭症教育:探索寄宿学校的创新实践
  • 30分钟学会css
  • http性能测试命令ab
  • 外网访问本地部署的 VMware ESXi 服务
  • ubuntu初始配置
  • C#—SynchronizationContext类详解 (同步上下文)
  • hashMap追问
  • Python 中利用装饰器实现多线程函数调用示例
  • 利用Deeplearning4j进行 图像识别
  • Quartus与Synopsys联合调试
  • Golang中的通道和缓冲区
  • 计算机网络相关术语
  • 2025加密风云:行业变革与未来趋势全景透视
  • 2025/1/4期末复习 密码学 按老师指点大纲复习
  • 【51项目】51单片机自制小霸王游戏机
  • 【SpringBoot3】Spring Boot 3.0 集成 Mybatis Plus
  • 医学图像分析工具02:3D Slicer || 医学影像可视化与分析工具 支持第三方插件
  • AI辅助的运维流程自动化:实现智能化管理的新篇章
  • connect to host github.com port 22: Connection timed out 的解决方法
  • R语言的数据结构
  • 城市供水管网多普勒超声波流量计,保障供水安全
  • 【游戏设计原理】46 - 魔杖
  • 一种新的混合大模型架构:TITAN
  • 【Python运维】使用Python与Docker进行高效的容器化应用管理
  • Tomcat性能优化与负载均衡实现