mini-react 目标:在页面中呈现app

104 阅读2分钟

目标:在页面中呈现APP。

要求:我们写的mini-react api和react的api对齐。

学习方法:将大任务拆解为小任务,确保清晰的学习路径。

任务拆解:

image.png

V0.1

vdom 写死,dom 写死。

/** 
 * main.js vdom 写死,dom 写死; 
 */
const element = document.createElement("div");
element.id = "app";
document.querySelector("#root").append(element);

const textElement = document.createTextNode("");
textElement.nodeValue = "app";
element.append(textElement);

<!-- index.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>

OK, 页面出现app,成功的第一步,到下一个小任务。

V0.2

vdom动态生成,dom写死

/**
 * main.js vdom 动态生成,dom 写死;
 */

// 数据结构设计  vdom 是一个 js object,思考里面有什么属性
const textEl = {
  type: "TEXT_ELEMENT",
  props: {
    nodeValue: "app",
    children: [],
  },
};

const el = {
  type: "div",
  props: {
    id: "app",
    children: [textEl],
  },
};

const element = document.createElement(el.type);
element.id = el.props.id;
document.querySelector("#root").append(element);

const textElement = document.createTextNode("");
textElement.nodeValue = textEl.props.nodeValue;
element.append(textElement);

写死的数据结构,进一步复用

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children,
    },
  };
};

const createElementText = (nodeValue) => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue,
      children: [],
    },
  };
};

const textEl = createElementText("app");
const App =createElement("div", { id: "app" }, textEl);

const element = document.createElement(App.type);
element.id = App.props.id;
document.querySelector("#root").append(element);

const textElement = document.createTextNode("");
textElement.nodeValue = textEl.props.nodeValue;
element.append(textElement);

OK,vdom动态生产完成,下一步dom动态生成,很有打怪升级的感觉。

V0.3

vdom动态生成,dom动态生成

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children,
    },
  };
};

const createElementText = (nodeValue) => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue,
      children: [],
    },
  };
};

const textEl = createElementText("app");
const App = createElement("div", { id: "app" }, textEl);

const render = (App, container) => {
  const dom =
    App.type === "TEXT_ELEMENT"
      ? document.createTextNode(App.type)
      : document.createElement(App.type);

  // 处理props
  Object.keys(App.props).forEach((key) => {
    if (key !== "children") {
      dom[key] = App.props[key];
    }
  });
  
  // 处理children
  App.props.children?.forEach((child) => {
    render(child, dom);
  });
  container.append(dom);
};

render(App, document.querySelector("#root"));

const textEl = createElementText("app"); const App = createElement("div", { id: "app" }, textEl); 这里还可以精简

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
       return typeof child === "string" ? createElementText(child) : child;
      }),
    },
  };
};

const createElementText = (nodeValue) => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue,
      children: [],
    },
  };
};
const App = createElement("div", { id: "app" }, "app");

const render = (App, container) => {
  const dom =
    App.type === "TEXT_ELEMENT"
      ? document.createTextNode(App.type)
      : document.createElement(App.type);

  // 处理props
  Object.keys(App.props).forEach((key) => {
    if (key !== "children") {
      dom[key] = App.props[key];
    }
  });

  // 处理children
  App.props.children?.forEach((child) => {
    render(child, dom);
  });
  container.append(dom);
};

render(App, document.querySelector("#root"));

好了,完成前面的三个小任务,我们还有一个要求,和react api对齐。 我们现在的代码结构是这样的:

Snipaste_2024-03-23_14-31-12.png

我们的要求是这样的:

Snipaste_2024-03-23_14-32-35.png

那就开始封装了。

// core/ReactDom.js

import React from "./React.js"; //引入需要注意要写全文件名

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

export default ReactDom;

// core/React.js

const render = (App, container) => {
  const dom =
    App.type === "TEXT_ELEMENT"
      ? document.createTextNode(App.type)
      : document.createElement(App.type);

  // 处理props
  Object.keys(App.props).forEach((key) => {
    if (key !== "children") {
      dom[key] = App.props[key];
    }
  });

  // 处理children 用到了递归
  App.props.children?.forEach((child) => {
    render(child, dom);
  });
  container.append(dom);
};

const createElement = (type, props, ...children) => {
  return {
    type,
    props: {
      ...props,
      children: children.map((child) => {
        return typeof child === "string" ? createElementText(child) : child;
      }),
    },
  };
};

const createElementText = (nodeValue) => {
  return {
    type: "TEXT_ELEMENT",
    props: {
      nodeValue,
      children: [],
    },
  };
};

const React = {
  render,
  createElement,
};

export { createElement };
export default React;   

// App.js

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

export default App; 

// main.js

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

ReactDom.createRoot(document.querySelector("#root")).render(App);

Snipaste_2024-03-23_14-48-57.png

好了,小目标完成。