实现最简单的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
相关推荐
gqkmiss24 分钟前
Chrome 浏览器 131 版本开发者工具(DevTools)更新内容
前端·chrome·浏览器·chrome devtools
Summer不秃29 分钟前
Flutter之使用mqtt进行连接和信息传输的使用案例
前端·flutter
旭日猎鹰33 分钟前
Flutter踩坑记录(二)-- GestureDetector+Expanded点击无效果
前端·javascript·flutter
Viktor_Ye40 分钟前
高效集成易快报与金蝶应付单的方案
java·前端·数据库
hummhumm42 分钟前
第 25 章 - Golang 项目结构
java·开发语言·前端·后端·python·elasticsearch·golang
乐闻x1 小时前
Vue.js 性能优化指南:掌握 keep-alive 的使用技巧
前端·vue.js·性能优化
一条晒干的咸魚1 小时前
【Web前端】创建我的第一个 Web 表单
服务器·前端·javascript·json·对象·表单
Amd7941 小时前
Nuxt.js 应用中的 webpack:compiled 事件钩子
前端·webpack·开发·编译·nuxt.js·事件·钩子
生椰拿铁You2 小时前
09 —— Webpack搭建开发环境
前端·webpack·node.js
狸克先生2 小时前
如何用AI写小说(二):Gradio 超简单的网页前端交互
前端·人工智能·chatgpt·交互