实现简易的 mini-react,并在页面中呈现内容

下面是实现的基本思路:

先实现最简单的 mini-react

在浏览器中展示一个简单的字符串 "app"。

index.html 代码如下:

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>

main.js 实现了简单的 DOM 操作:

js 复制代码
const dom = document.createElement("div");
dom.id = "app";
document.querySelector("#root").append(dom);

const textNode = document.createTextNode("");
textNode.nodeValue = "app";
dom.append(textNode);

这里使用了 document.createTextNode("") 创建一个空的文本节点 textNode,然后通过 textNode.nodeValue = "app" 将文本节点的值设置为 "app",这样就可以在页面上就可以看到"app"了

接下来,引入vdom概念,对代码进行优化。

引入虚拟 DOM(vdom)

第一步,将vdom写死以及dom渲染也写死 main.js 文件更新如下:

js 复制代码
// type props children
const textEl = {
    type: "TEXT_ELEMENT",
    props: { nodeValue: "app", children: [] }
}
const el = {
    type: "div",
    props: {
        id: "app", children: [textEll]
    }
}
const dom = document.createElement(el.type);
dom.id = el.props.iddocument.querySelector("#root").append(dom);
const textNode = document.createTextNode("");
textNode.nodeValue = textEl.props.nodeValuedom.append(textNode)

此时在页面上也可以显示字符串'app',接着继续优化代码,向官方API的形式靠拢

第二步,将vdom改成动态生成,dom渲染还是写死 此时可以抽取出两个函数,一个是创建元素节点的 createElement,另一个是创建文本节点的 createTextNode; main.js 文件更新如下:

js 复制代码
function createTextNode(text) {
    return {
        type: "TEXT_ELEMENT",
        props: {
            nodeValue: text,
            children: []
        },
    }
}
function createElement(type, props, ...children) {
  return {
    type: type,
    props: {
      ...props,
      children: children.map((child) => {
        // 如果是字符串,则处理成文本节点
        return typeof child === "string" ? createTextNode(child) : child;
      }),
    },
  };
}

const textEl = createTextNode("app")
const App = createElement("div", { id: "app" }, createTextNode("app"));
const dom = document.createElement(App.type);
dom.id = App.props.id;
document.querySelector("#root").append(dom);
const textNode = document.createTextNode("");
textNode, nodeValue = textEl.props.nodeValue;
dom.append(textNode)

第三步,将vdom以及dom都改成动态生成,这时候需要添加 render 函数;同时为了更接近 React 的写法,将 render 封装成 ReactDOM 的调用形式, main.js 文件更新如下:

javascript 复制代码
function createTextNode(text) {
    return {
        type: "TEXT_ELEMENT",
        props: {
            nodeValue: text,
            children: []
        },
    }
}
function createElement(type, props, ...children) {
  return {
    type: type,
    props: {
      ...props,
      children: children.map((child) => {
        // 如果是字符串,则处理成文本节点
        return typeof child === "string" ? createTextNode(child) : child;
      }),
    },
  };
}
/** 
 * 渲染函数,将虚拟DOM元素渲染到实际DOM容器中 
 * @param {object} el - 虚拟DOM元素 
 * @param {Element} container - 实际要添加进DOM容器 
 */
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];
        }
    });

    // 递归渲染子节点
    el.props.children.forEach((child) => {
        render(child, dom);
    });

    // 将DOM节点添加到容器中
    container.append(dom);
}
const ReactDOM = {
    createRoot(container) {
        return {
            render(App) {
                return render(App, container);
            },
        };
    },
};

// 使用
const App = createElement(
    "div",
    { id: "app" },
    "app",
    createElement("p", { id: "text" }, "Hello, App!")
);

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

现在运行下项目,查看效果

最后,对代码结构进行整理。将 main.js 中的方法抽取到 core 目录下的 React.jsReactDOM.js 文件中,然后再添加一个 App.js 文件。

目录结构如下:

css 复制代码
├─ App.js
├─ index.html
├─ main.js
├─ core
|  ├─ React.js
|  └- ReactDOM.js

React.js 文件:

javascript 复制代码
function createTextNode(text) {
    return {
        type: "TEXT_ELEMENT",
        props: {
            nodeValue: text,
            children: [],
        },
    };
}

function createElement(type, props, ...children) {
    return {
        type: type,
        props: {
            ...props,
            children: children.map((child) => {
                // 如果是字符串,则处理成文本节点
                return typeof child === "string" ? createTextNode(child) : child;
            }),
        },
    };
}

/** 
 * 渲染函数,将虚拟DOM元素渲染到实际DOM容器中 
 * @param {object} el - 虚拟DOM元素 
 * @param {Element} container - 实际要添加进DOM容器 
 */
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];
        }
    });

    // 递归渲染子节点
    el.props.children.forEach((child) => {
        render(child, dom);
    });

    // 将DOM节点添加到容器中
    container.append(dom);
}

const React = { createElement, render };
export default React;

ReactDOM.js 文件:

javascript 复制代码
import React from "./React.js";

const ReactDOM = {
    createRoot(container) {
        return {
            render(App) {
                return React.render(App, container);
            },
        };
    },
};

export default ReactDOM;

App.js 文件:

javascript 复制代码
import React from "./core/React.js";

const App = React.createElement(
    "div",
    { id: "app" },
    "app",
    React.createElement("p", { id: "text" }, "Hello, App!")
);

export default App;

main.js 文件:

javascript 复制代码
import ReactDOM from "./core/ReactDOM.js";
import App from "./App.js";

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

这时候,代码结构和 React 官方的 API 比较接近了。 按照文章开头的实现思路,将大任务拆解为小任务,更清晰的去完善代码。 当然,还有许多需要继续完善的,可以带着问题继续优化代码,例如支持jsx的写法以及dom树大的时候渲染卡顿需要实现任务调度器等

继续努力💪

相关推荐
燃先生._.4 小时前
Day-03 Vue(生命周期、生命周期钩子八个函数、工程化开发和脚手架、组件化开发、根组件、局部注册和全局注册的步骤)
前端·javascript·vue.js
高山我梦口香糖5 小时前
[react]searchParams转普通对象
开发语言·前端·javascript
black^sugar5 小时前
纯前端实现更新检测
开发语言·前端·javascript
2401_857600957 小时前
SSM 与 Vue 共筑电脑测评系统:精准洞察电脑世界
前端·javascript·vue.js
2401_857600957 小时前
数字时代的医疗挂号变革:SSM+Vue 系统设计与实现之道
前端·javascript·vue.js
GDAL7 小时前
vue入门教程:组件透传 Attributes
前端·javascript·vue.js
小白学大数据7 小时前
如何使用Selenium处理JavaScript动态加载的内容?
大数据·javascript·爬虫·selenium·测试工具
2402_857583497 小时前
基于 SSM 框架的 Vue 电脑测评系统:照亮电脑品质之路
前端·javascript·vue.js
java_heartLake8 小时前
Vue3之性能优化
javascript·vue.js·性能优化