不只是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怎么写?
javascript 复制代码
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

javascript 复制代码
//创建文本对象虚拟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并将其导出

javascript 复制代码
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

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

export default ReactDOM;
创建ReactDOM

/main.js

javascript 复制代码
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

javascript 复制代码
{
  "scripts": {
    "test": "vitest"
  },
  "devDependencies": {
    "vitest": "^1.1.3"
  }
}

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

/tests/createElement.spec.js

javascript 复制代码
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

html 复制代码
<!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>
相关推荐
崔庆才丨静觅1 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了2 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅2 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax