1. Build your own React 阅读笔记——createElement和render实现

本文主要记录阅读Build your own react一些记录,虽然该博客构建的ReactV16.8(所以博客中react代码都是V16的写法),和现在ReactV19版本有一定差别,但V16.8开始包含hooks组件,主体渲染流程是基本一致的,原文最后目的是通过砍掉react优化策略内容,保留最本质重要思想,并实现didact框架(200多行代码)模拟react基本渲染过程

我们可以收获:

  • 知道react写法对应的原生JS实现都做了哪些事情;
  • 知道react背后渲染流程步骤有哪些,以及这么做理由;
  • 明白上面这些,自己再实现一个简约版React(useState),加深对React的理解;

react写法对应原生JS实现

reactjsx是一种声明式写法,开发者无需直接操作dom,只需在函数组件返回中写jsx需要渲染dom结构,然后react帮助我们去更新dom,所以react是做了一层抽象封装,最后react代码也是会被编译为对dom操作,所以我们的角度(从react代码,看其背后原生js做的事情)。

tsx 复制代码
const element = <h1 title="foo">Hello</h1>
const container = document.getElementById("root")
ReactDOM.render(element, container)  //V18之前写法

注:V18使用ReactDOM.createRoot(root).render(<App />) 来渲染root节点以支持并发模式。

上面的jsx写法const element = <h1 title="foo">Hello</h1> 通过babel调用React内置的createElement方法,实际上会被转义一个js对象记录节点的type类型propschildren等基本信息 而ReactDOM.render(element, container),实际上做就是根据传入的React Element对象生成对应dom元素并挂载到容器root节点上。

js 复制代码
// jsx 对应js对象
const element = {
    type: "h1",
    props: {
        title: "foo",
        children: "Hello",
   }
},
const container = document.getElementById("root")

// ReactDOM.render对应做的事情
const node = document.createElement(element.type)
node["title"] = element.props.title
const text = document.createTextNode("")
text["nodeValue"] = element.props.children
node.appendChild(text)
container.appendChild(node)

createElement

React.createElement

为了复刻一个react,我们要做就是实现createElement,在编译时让babel调用我们实现createElement转为js对象,对着react.createElement实现看看参数列表,基本都是包含节点的type类型propschildren信息,不同时返回节点类型不同,并且把input单拎出来定义。

tsx 复制代码
 createElement('img',{src:'xxxx'})

自定义版createElement

自定义版不需要考虑很多,保留基本逻辑就行。

tsx 复制代码
function createElement(type, props, ...children) {
 return {
   type,
   props: {
     ...props,
     children: children.map((child) =>
       typeof child === "object" ? child : createTextElement(child)
     ),
   },
 };
}

function createTextElement(text) {
 return {
   // 这里将文本类型,定义TEXT_ELEMENT
   type: "TEXT_ELEMENT",
   props: {
     nodeValue: text,
     children: [],
   },
 };
}
tsx 复制代码
/** @jsx Didact.createElement */
const element = (
   <div id="foo">
       <span>图片描述</span>
       <img src='xxxx'/>
   </div>)
   
// 上面jsx会转义成下面的babel调用
const element = Didact.createElement(
   "div",
   { id:"foo"},
   Didact.createElement("span", null, "图片描述"),
   Didact.createElement("img")
),

// 对应js对象
const element= {
    type: 'div',
    props:{
        id:"foo",
        children:[{
            type: 'span',
            props:{
                children:[{
                    type:'TEXT_ELEMENT',
                     props: {
                     nodeValue: '图片描述',
                     children: [],
                   },
                }]
            },
            {
             type: 'img',
             props:{
                 children:[]
             }
            }
        }]
    }
}

render函数

有了需要确认渲染结构elements对象,我们只需要传入render函数执行document.createElement等原生相关方法

ts 复制代码
function render(element, container) {
    const dom = element.type == "TEXT_ELEMENT"
? document.createTextNode("") : document.createElement(element.type)

   const isProperty = key => key !== "children"

   Object.keys(element.props).filter(isProperty)
   .forEach(name => {
       dom[name] = element.props[name]
   })

   element.props.children.forEach(child =>
       render(child, dom)
   )
   
   container.appendChild(dom)
}

小结

有了createElementrender,我们似乎可以通过写jsx代码到渲染真正的dom元素,但是render里只有添加dom,更新和删除并没有实现,并且由于render是递归调用的,这意味着一旦开始渲染,就不会停止执行,如果元素树很大,可能会阻塞主线程太长时间。如果浏览器需要执行高优先级的操作,例如处理用户输入或保持动画流畅,则必须等到渲染完成。 所以还需要进行优化实现并发模式渲染fiber比较渲染提交

相关推荐
崔庆才丨静觅2 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60612 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了3 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅3 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅3 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅3 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment3 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅4 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊4 小时前
jwt介绍
前端
爱敲代码的小鱼4 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax