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

相关推荐
黄毛火烧雪下8 分钟前
React返回上一个页面,会重新挂载吗
前端·javascript·react.js
浮华似水11 分钟前
接口隔离原则在前端的应用
前端
想要打 Acm 的小周同学呀1 小时前
跨域的解决方案
前端·跨域·请求
安冬的码畜日常2 小时前
【D3.js in Action 3 精译_025】3.4 让 D3 数据适应屏幕(中)—— 线性比例尺的用法
前端·javascript·信息可视化·数据可视化·d3.js·d3比例尺·javascript可视化
fury_1232 小时前
nodejs:实现大文件的分段上传
开发语言·前端·javascript
无限大.2 小时前
0基础学前端 day6 -- 搭建github pages静态网址
前端·github
anyup_前端梦工厂3 小时前
JS设计模式之状态模式:优雅地管理应用中产生的不同状态
前端·javascript·设计模式
光影少年4 小时前
Vue Mini基于 Vue 3 的小程序框架
前端·vue.js·小程序
Minyy114 小时前
小程序-全局数据共享
开发语言·前端·小程序
linpengteng4 小时前
使用 Flutter 开发数字钱包应用(Dompet App)
前端·flutter·firebase