实现最简单的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
相关推荐
0wioiw012 分钟前
Flutter基础(前端教程④-组件拼接)
前端·flutter
花生侠37 分钟前
记录:前端项目使用pnpm+husky(v9)+commitlint,提交代码格式化校验
前端
一涯44 分钟前
Cursor操作面板改为垂直
前端
我要让全世界知道我很低调1 小时前
记一次 Vite 下的白屏优化
前端·css
1undefined21 小时前
element中的Table改造成虚拟列表,并封装成hooks
前端·javascript·vue.js
蓝倾1 小时前
淘宝批量获取商品SKU实战案例
前端·后端·api
comelong2 小时前
Docker容器启动postgres端口映射失败问题
前端
花海如潮淹2 小时前
硬件产品研发管理工具实战指南
前端·python
用户3802258598242 小时前
vue3源码解析:依赖收集
前端·vue.js