实现最简单的React

如果用 react创建一个App,在页面中输出app,那么大概的代码是这样的

现在我们尝试自己实现上述的api达到同样的效果,我们分成几个步骤来实现这个需求

1、vdom和渲染全部写死

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

javascript 复制代码
//v1
   const dom = document.createElement("div");
   dom.id = "app";
   document.querySelector("#root").append(dom);
   const textNode = document.createTextNode("");
   textNode.nodeValue = "app";
   dom.append(textNode);

这样页面上就会渲染出app

2、vdom 动态生成,dom 渲染写死

其实虚拟dom就是一个js对象,type,props还有后代children 可以如下表示

javascript 复制代码
//文本
const TxtEl = {
  type: "TEXT_ELEMENT",
  props: {
    nodeValue: "app",
    children: [],
  },
};
//代表dom元素
const el = {
  type: "div",
  props: {
    id:'app',
    children: [TxtEl],
  },
};

//渲染过程
   const dom = document.createElement(el.type);
   dom.id = el.props.id;
   document.querySelector("#root").append(dom);
   const textNode = document.createTextNode("");
   textNode.nodeValue = TxtEl.props.nodeValue;
   dom.append(textNode);

再进一步可以将vdom的生成写成一个函数

javascript 复制代码
unction createTextNode(text){
    return {
        type: "TEXT_ELEMENT",
        props: {
          nodeValue: text,
          children: [],
        },
      }
}
//这里。。。形成了剩余参数,children就是一个数组
function createElement(type,props,...children){
  return {
    type,
    props:{
        ...props,
        children
    }
  }
}
//相应的上述代码就演变成

  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 = TxtEl.props.nodeValue;
   //添加
   dom.append(textNode);

至此vdom的创建已经可以动态生成,下面来改造渲染过程

3、vdom 动态生成,dom动态生成

仔细观察上述代码,不难发现整个渲染过程就是 1)创建dom 2)设置dom属性 3)添加dom到容器中 这三步,那么我们可以根据这个流程创建 render 函数

javascript 复制代码
const render=(el,container)=>{
  //createEl
  const dom=el.type==="TEXT_ELEMENT"?document.createTextNode(""):document.createElement(el.type)
  //设置pros
  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 App=createElement("div",{id:'App'},createTextNode("app"))
//创建节点
const container = document.querySelector("#root");
render(App,container)

这样我们就完成了动态创建vdom和动态渲染的过程

#和React api 保持一致

首先我们对上述代码做一些小优化,目标是可以这样创建根组件App const App=createElement("div",{id:'App'},"app")

javascript 复制代码
function createElement(type,props,...children){
  return {
    type,
    props:{
        ...props,
        children:children.map(child=>{
             //对child作处理如果是 string 直接返回文本节点
            return typeof child==='string'?createTextNode(child):child
        })
    }
  }
}

接下去就是对api的一些改造,主要是要构造一个ReactDom对象,接受一个根容器,返回的对象中暴露render方法,render方法执行时接受根组件,然后再调用我们自己写的render方法将根容器和组件传入即可

javascript 复制代码
const ReactDom={
    createRoot(container){
        return {
            render(App){
              render(App,container)
            }
        }
    }
}
const App=createElement("div",{id:'App'},"app")
ReactDom.createRoot(document.querySelector("#root")).render(App)

大功告成!

最终代码

为了后续的开发,我们对代码做一些整理,结构如下

main.js

javascript 复制代码
import ReactDom from "./core/reactDom.js";
import App from "./App.js";
ReactDom.createRoot(document.querySelector("#root")).render(App)

App.js

javascript 复制代码
import React from "./core/React.js"
const App=React.createElement("div",{id:'App'},"app",' hi-',' minireact')
export default App

core/React.js

javascript 复制代码
const render=(el,container)=>{
    //createEl
    const dom=el.type==="TEXT_ELEMENT"?document.createTextNode(""):document.createElement(el.type)
    //设置pros
    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)
  
  }

function createElement(type,props,...children){
    return {
      type,
      props:{
          ...props,
          children:children.map(child=>{
              return typeof child==='string'?createTextNode(child):child
          })
      }
    }
  }

function createTextNode(text){
    return {
        type: "TEXT_ELEMENT",
        props: {
          nodeValue: text,
          children: [],
        },
      }
}


const React={
    render,
    createElement
}

export default React

core/ReactDom.js

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

export default ReactDom
相关推荐
智能化咨询12 分钟前
【Linux】【实战向】Linux 进程替换避坑指南:从理解 bash 阻塞等待,到亲手实现能执行 ls/cd 的 Shell
前端·chrome
Anson Jiang15 分钟前
浏览器标签页管理:使用chrome.tabs API实现新建、切换、抓取内容——Chrome插件开发从入门到精通系列教程06
开发语言·前端·javascript·chrome·ecmascript·chrome devtools·chrome插件
掘金安东尼18 分钟前
黑客劫持:周下载量超20+亿的NPM包被攻击
前端·javascript·面试
剑亦未配妥1 小时前
移动端触摸事件与鼠标事件的触发机制详解
前端·javascript
人工智能训练师7 小时前
Ubuntu22.04如何安装新版本的Node.js和npm
linux·运维·前端·人工智能·ubuntu·npm·node.js
Seveny077 小时前
pnpm相对于npm,yarn的优势
前端·npm·node.js
yddddddy8 小时前
css的基本知识
前端·css
昔人'8 小时前
css `lh`单位
前端·css
Nan_Shu_61410 小时前
Web前端面试题(2)
前端
知识分享小能手10 小时前
React学习教程,从入门到精通,React 组件核心语法知识点详解(类组件体系)(19)
前端·javascript·vue.js·学习·react.js·react·anti-design-vue