实现最简单的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
相关推荐
小堂子这厢有礼了8 小时前
Chet.QuartzNet.UI v2.3.0 大更新!表格重构 + 系统配置 + 分析页全面升级!
前端·后端·ui·重构·c#·vue
程序员爱钓鱼8 小时前
Rust 控制流 if 详解:条件判断与 if 表达式
前端·后端·rust
Listen·Rain9 小时前
Vue3中computed详解
前端·javascript·vue.js
kyle~9 小时前
Swagger ---基于 描述文件 生成 交互式API文档 的工具
前端·后端·规格说明书·说明文档
世界哪有真情18 小时前
拿人类意识卡 AI?等于用 bug 验收正式产品
前端·人工智能·后端
Listen·Rain19 小时前
Vue3:setup详解
前端·javascript·vue.js
Patrick_Wilson20 小时前
修好 bug 只是开始:一次由监控需求反向重构日志结构的复盘
前端·监控·数据可视化
kyriewen20 小时前
面试官让我优化一个卡死的表格,我打开 Claude 五秒重写,他放下咖啡问我要不要来当组长
前端·javascript·面试
小粉粉hhh20 小时前
React(二)——dom、组件间通信、useEffect
前端·javascript·react.js
Csvn21 小时前
🤖 AI 生成前端代码的 5 个典型翻车现场及修复指南
前端