7天学习mini-react 第一天

上周参与了(催学社 (cuixueshe.com))的 《mini-react 游戏副本》 课程,老师喜欢通过对问题的拆分及小步骤的开发方式来授课,展现出完整的解决编程问题背后的思考过程。这也是对我收获最大的地方。

Day 1

01 实现最简 mini-react

小步快步,在浏览器显示一个 Hello, mini-React!

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>

main.js代码

const dom = document.createElement("div");
dom.id = "app";
document.querySelector("#root").append(dom);

const textNode = document.createTextNode("");
textNode.nodeValue = "Hello, mini-React!";
dom.append(textNode);

很简单的 dom 操作。这里的注意点就是document.createTextNode("") 创建了一个空的文本节点 textNode ,通过设置 textNode.nodeValue = "Hello, mini-React!",将文本节点的值设置为 “Hello, mini-React!”。

为什么要用 createTextNode 来添加文本节点?innerText不行吗?

createTextNode创建一个文本节点对象,可以保留对它们的引用,以便有选择地从元素中删除文本,而不删除构成该对象的其他文本。这样的文本的操作在逻辑上更加清晰,因为它是作为一个节点来处理的。这在处理复杂的 dom 结构或者动态内容时可能会更有助于代码的组织和理解。

02 引入虚拟 dom (vdom)

现在使用 vdom 来表达 dom 结构,例如要实现如下的一个dom 结构,要怎么处理呢?

const el = {
  type: 'div', // 元素类型,可以是HTML标签名或React组件
  props: {
    id: 'app', // 元素的属性,如class、id等
    children: [ // 子节点
	    {
		    type: 'TEXT_ELEMENT',
		    props: {
			    nodeValue: 'Hello, mini-React!',
			    children: []
		    }
	    }
     ]
  }
}

我们可以分成两个方法,一个是创建元素节点的 createElement 方法,和创建文本的节点的createTextNode 方法。

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;
      }),
    },
  };
}

那有了这些,就差在页面上显示出来了,现在来实现 render 方法。

/**
 * 渲染函数,将虚拟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);
}

使其尽量和React 的写法一致,我们把render封装成ReactDOM的调用形式

const ReactDOM = {
  createRoot(container) {
    return {
      render(App) {
        return render(App, container);
      },
    };
  },
};
// 使用
const App = createElement(
  "div",
  { id: "app" },
  "Hello, mini-React!",
  createElement("p", { id: "world" }, "Hello, world!")
);

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

现在用 Visual Studio Code 的 Live Server 跑起来看看吧。
请添加图片描述

O K 😃 !

下面我们来重构一下代码,先将main.js中的方法抽离到core目录下的 React.jsReactDOM.js ,然后再添加一个App.js 文件。

先看目录结构

├─ App.js
├─ index.html
├─ main.js
├─ core
|  ├─ React.js
|  └- ReactDOM.js

React.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;
      }),
    },
  };
}
/**
 * 渲染函数,将虚拟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 文件

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

App.js 文件

import React from "./core/React.js";

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

export default App;

main.js 文件

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

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

到这里就与React官方的API 写法差不多了。

03 使用JSX

使用vite 来构建一个空项目

pnpm create vite mini-react --template vanilla 

pnpm i 

将多余文件删除,只保留 package.json index.html main.js
先将main.jsApp.js 文件名修改成 main.jsxApp.jsx 。要注意 index.html中的引入文件名也要修改哦。

修改App.jsx 内容

import React from "./core/React.js";

const App = <div id="app">Hello, mini-React!</div>

export default App;

运行项目 pnpm run dev
会发现居然能直接显示出来

请添加图片描述

为什么会直接显示出来呢?按正常理解不是应该通过 babel 来编译吗?

因为 Vite(功能 | Vite 官方中文文档 (vitejs.dev)) 对.jsx.tsx 文件是开箱即用。JSX 的转译是通过 esbuild 来实现的。

今天就先学习到这里。

  • 21
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
使用html-to-react可以将HTML代码转换为React组件。下面是一个示例代码,展示如何使用html-to-react将HTML字符串转换为React组件: ```javascript import React from 'react'; import ReactDOMServer from 'react-dom/server'; import HtmlToReact from 'html-to-react'; class MyComponent extends React.Component { render() { return ( <div>{this.props.children}</div> ); } } const htmlInput = '<div><h1>Title</h1><p>Some text</p></div>'; const processNodeDefinitions = new HtmlToReact.ProcessNodeDefinitions(React); const processingInstructions = [ { // Custom <div> processing shouldProcessNode: function(node) { return node.name === 'div'; }, processNode: function(node, children) { return React.createElement(MyComponent, {}, children); } }, { // Default processing shouldProcessNode: function(node) { return true; }, processNode: processNodeDefinitions.processDefaultNode, }, ]; const reactElement = HtmlToReact.htmlToReactParser.parseWithInstructions(htmlInput, () => true, processingInstructions); const reactHtml = ReactDOMServer.renderToStaticMarkup(reactElement); console.log(reactHtml); ``` 在上面的示例中,我们首先定义了一个React组件`MyComponent`,它包含一个`<div>`元素来显示子元素。然后,我们定义了一个HTML字符串`htmlInput`,它包含一个`<div>`元素和一些子元素。接下来,我们使用`html-to-react`库中的`htmlToReactParser`函数将HTML字符串转换为React组件。我们还定义了一个`processingInstructions`数组,其中包含两个对象,一个用于处理`<div>`元素,另一个用于处理默认情况。最后,我们使用`ReactDOMServer`中的`renderToStaticMarkup`函数将React组件转换为HTML字符串,并将其打印到控制台上。 需要注意的是,`html-to-react`库提供了许多其他处理HTML节点的函数和选项,可以根据需要进行自定义。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值