实现最简单的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
相关推荐
twins352044 分钟前
解决Vue应用中遇到路由刷新后出现 404 错误
前端·javascript·vue.js
qiyi.sky1 小时前
JavaWeb——Vue组件库Element(3/6):常见组件:Dialog对话框、Form表单(介绍、使用、实际效果)
前端·javascript·vue.js
煸橙干儿~~1 小时前
分析JS Crash(进程崩溃)
java·前端·javascript
安冬的码畜日常1 小时前
【D3.js in Action 3 精译_027】3.4 让 D3 数据适应屏幕(下)—— D3 分段比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·分段比例尺
l1x1n02 小时前
No.3 笔记 | Web安全基础:Web1.0 - 3.0 发展史
前端·http·html
昨天;明天。今天。2 小时前
案例-任务清单
前端·javascript·css
zqx_73 小时前
随记 前端框架React的初步认识
前端·react.js·前端框架
惜.己3 小时前
javaScript基础(8个案例+代码+效果图)
开发语言·前端·javascript·vscode·css3·html5
什么鬼昵称4 小时前
Pikachu-csrf-CSRF(get)
前端·csrf
长天一色4 小时前
【ECMAScript 从入门到进阶教程】第三部分:高级主题(高级函数与范式,元编程,正则表达式,性能优化)
服务器·开发语言·前端·javascript·性能优化·ecmascript