mini-react 实现 - 在页面中呈现app

为什么学习mini-react要从在页面上渲染app开始? 我本人使用react也有三年时间了,找过很多课程资料试图学习React源码,但不是很难看懂就是看完就忘,对一个很高级的创造规则的技术非要强制将它的知识体系硬生生的塞进大脑,我尝试了很多次,结果都很不理想^_^,我一度怀疑是我脑子有问题🤣,直到看到崔胡子叔叔的课程,才恍然大悟、茅塞顿开。

无论是Vue, React,还是angular,对于前端来说,从最最直观的视觉角度来说就是在页面如何呈现内的,所以mini-react就是从最基础的创建dom,渲染dom这种简单的方式,渐进式的去学习React源码。非常适合初学者学习React源码。

实现思路:

React 在页面中呈现App

在页面上呈现一个App,代码大体如下:

HTML 复制代码
<div id="app">
  App
</div>

第一阶段

markdown 复制代码
1. 创建一个dom, id为app的div

2. 在dom里面创建一个文本节点

3. 渲染dom
HTML 复制代码
<!doctype html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <link rel="icon" type="image/svg+xml" href="/vite.svg" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Vite + React</title>
  </head>
  <body>
    <div id="root"></div>
    <script type="module" src="/src/main.js"></script>
  </body>
</html>

第一阶段实现方式:用非常简单的方式实现在页面呈现app

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

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

第二阶段

这个阶段我们要使用react当中的一些思想来重构以上代码。

  1. 首先聊聊dom节点,很多情况下它是有层级的,比如:
HTML 复制代码
<div id="app">
  <div>
    <div>
      <div>
        <div>
         App
        </div>
      </div>
    </div>
  </div>
</div>

甚至有很多层,这个在我们创建dom阶段是个未知数,我们不确定它有多少层,而且它是树状结构,有层级的。

  1. 还有需要考虑到的React当中有虚拟DOM , 这里简称VDom 【Virtual DOM】,其数据结构就是一个Object。这对象需要哪些属性呢?结合第一阶段的实现需要有类型区分type , 分析是树状结构有层级之分children, 每种类型的节点有自己的属性props

所以结合第一阶段的实现思路:

  1. 创建一个对象element,数据结构是一个对象, 包含type, children, props
  2. 只需要将之前的写死的数据替换成对象中的对应的值即可。
JavaScript 复制代码
const textElement = {
  type:"text_element",
  props:{
    nodeValue:"app",
    children:[]
  }
}
const element = {
  type:"div",
  props:{
    id:"app",
    children:[textElement]
  }
}

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

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

第三阶段

到这里其实已经很明显,以上我们创建的节点即便是引入和VDom的概念,但很显然还是死值,所以这一阶段我们的重点是,***如何动态创建VDOM?***在这个阶段其实也比较简单,我们使用函数的方式,通过传参来完成节点的创建。

JavaScript 复制代码
const createTextNodeElement = (text) => {
  return {
    type: "text_element",
    props: {
      nodeValue: text,
      children: []
    }
  }
}

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

第四阶段

第三阶段实现了动态创建VDOM,然后去渲染;但是很明显无论是createElement,还是createTextNodeElement,我们最终生成的dom结构是比较单一的,我们之前基于VDOM的特点:树状结构,有层级,加入了对应的属性,但是远远不够,VDOM的结构才是重点,它不可能是我们demo的那么简单,所以我们还需要动态的来生成未知层级的结构,很显然需要用到递归的思想。

一旦结构变的更复杂,怎么创建节点呢?首先思考我们说结构复杂,那肯定是复杂在children这个属性上了,它可能会有很多层,所以会如何创建节点 createElement ?如何渲染 render?实现思路如下:

JavaScript 复制代码
const createTextNodeElement = (text) => {
  return {
    type: "text_element",
    props: {
      nodeValue: text,
      children: []
    }
  }
}

const createElement = (type, props, ...children) => {
  return {
    type: type,
    props: {
      ...props,
      // 这里使用递归,在循环中去判断该创建文本节点,还是继续创建子节点
      children: children.map((child) => {
        return typeof child === "string" ? createTextNodeElement(child) : child
      })
    }
  }
}

const textNodeElement = createTextNodeElement("app");
// 创建一个多层级的节点
const App = createElement(
  "div",
  {id:"app"},
  "create app,",
  "by holin,",
  "welcome to mini-react"
)


function render(elment, container){
  const dom = el.type === "text_element" ? document.createTextNode("") 
              : document.createElement(element.type);
  // 设置属性
  Object.keys(element.props).forEach((key) => {
    if(key !== "children"){
      dom[key] = element.props[key]
    }
  })
  
  // 存在子孙结点,递归思想
  const children = element.props.children;
  if(!children){
    children.forEach((child) => {
      render(child,dom)
    })
  }
  container.append(dom)
}

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

第五阶段

模仿React的文件结构,优化和重构我们的代码。

github.com/HolinWang/m...

相关推荐
朝阳391 天前
react19【系列实用教程】setSearchParams
react.js
带娃的IT创业者1 天前
Puppeteer 深度解析:超越自动化测试的现代 Web 交互范式
前端·交互·puppeteer·可观测性·浏览器自动化·无头浏览器·web工程化
程序员爱钓鱼1 天前
Rust 所有权 Ownership 详解:理解内存安全的核心机制
前端·后端·rust
Mh1 天前
别再只会 `find` 了:Map 在前端业务里的真实用法
前端·javascript
陈随易1 天前
FFmpeg 9.0 发布,代号 Lei,音视频处理再升级
前端·后端·程序员
爱丶狸1 天前
Grafana_Zabbix_ImageRenderer_部署与前端操作手册
linux·前端·zabbix·grafana·kylin
用户059540174461 天前
把AI对话记忆存储测试从手工改成Playwright+pytest,覆盖率从20%提到96%,回归时间缩短90%
前端·css
kyriewen1 天前
别再这样写TypeScript了——Code Review中最常见的8个反模式
前端·javascript·typescript
名字还没想好☜1 天前
Next.js ‘use client‘ 到底加在哪:Server/Client Components 边界与常见报错
开发语言·前端·javascript·react·next.js
午安~婉1 天前
GitHub Token/ GitHub Stats统计显示图异常
前端·github·vercel·github token