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比较渲染提交

相关推荐
浮桥1 小时前
vue3实现pdf文件预览 - vue-pdf-embed
前端·vue.js·pdf
七夜zippoe1 小时前
前端开发中的难题及解决方案
前端·问题
Hockor2 小时前
用 Kimi K2 写前端是一种什么体验?还支持 Claude Code 接入?
前端
杨进军2 小时前
React 实现 useMemo
前端·react.js·前端框架
海底火旺2 小时前
浏览器渲染全过程解析
前端·javascript·浏览器
你听得到112 小时前
揭秘Flutter图片编辑器核心技术:从状态驱动架构到高保真图像处理
android·前端·flutter
驴肉板烧凤梨牛肉堡2 小时前
浏览器是否支持webp图像的判断
前端
Xi-Xu2 小时前
隆重介绍 Xget for Chrome:您的终极下载加速器
前端·网络·chrome·经验分享·github
摆烂为不摆烂3 小时前
😁深入JS(九): 简单了解Fetch使用
前端
杨进军3 小时前
React 实现多个节点 diff
前端·react.js·前端框架