react源码解析

文章详细介绍了React中jsx转化为虚拟DOM并最终挂载到真实DOM上的步骤。首先通过模拟React.createElement方法生成虚拟DOM,包括处理type、key、ref和props等核心属性。接着,利用render函数将虚拟DOM转换并添加到实际DOM结构中,涉及createDom和updateProps等函数,处理元素创建、属性更新和子元素渲染。整个过程与Vue的实现有相似之处。
摘要由CSDN通过智能技术生成

一. 实现jsx到真实dom的挂载

react 中jsx到真实dom的挂载大致可分为三步:

jsx模板语法 —— babel模板语法拆解使用React.createElement() 生成虚拟dom —— 虚拟dom通过render函数挂载到真实dom上。

其实很多的细节和vue很相似可以看前面我关于vue源码的记载。

1. 首先是模拟 React.createElement() 生成虚拟dom

看一下原来版本的虚拟节点

 这里我们主要来模仿其中的核心属性:typeoof这个就是节点类型(是节点元素还是文本),key用于diff算法,props里面保存三类配置:

(1)children 子元素

(2)样式属性

(3)其他属性值

ref用于指向生成的真实dom,type其实就是dom节点类型(div,span等等)。

首先是模拟生成vdom

import { REACT_ELEMENT } from "./stants"
import { toObject } from "./utils"
/**
 * @name 创建虚拟节点
 * @param {*} type dom类型 
 * @param {*} config 配置项
 * @param {*} children 子元素
 * @returns 返回虚拟节点对象
 */
function createElement(type, config, children) {
    let props = { ...config }
    let key = null, ref = null //用于diffing算法和构建真实dom
    if (config) {
        key = config.key ? config.key : null
        ref = config.ref ? config.ref : null
        delete config.key
        delete config.ref
    }
    if (config) {
        //有多个children
        if (arguments.length > 3) {
            props.children = Array.prototype.slice.call(arguments, 2).map(toObject) //获取所有的子元素
        } else if (arguments.length === 3) {
            props.children = toObject(children)
        }
    }
    //返回虚拟节点
    return {
        $$typeof: REACT_ELEMENT,
        type,//类型
        key,//diff算法优化
        ref,//获取真实dom
        props,
    }
}
const React = {
    createElement
}
export default React

然后引入的js一个symbol的枚举类,一个是负责检验dom的类型是节点元素还是文本

stants.js

//标所元素
export const REACT_ELEMENT = Symbol("react.element")
export const REACT_TEXT = Symbol("react.TEXT")

utlis

import { REACT_TEXT } from "./stants"
export function toObject(element) {
    return typeof element == 'string' || typeof element == 'number' ?
        { type: REACT_TEXT, content: element } : element
}

通过以上步骤就可以实现jsx到虚拟dom的转变

2. 将虚拟dom挂载为真实dom,这个就没什么细说的直接看代码就好

react-dom.js

import { REACT_ELEMENT, REACT_TEXT } from "./stants"
/**
 * 
 * @param {*} vdom 虚拟节点
 * @param {*} container 容器
 */
function render(vdom, container) {
    let newDom = createDom(vdom)
    container.appendChild(newDom)
}
/**
 * @name 生成真实dom
 * @param {*} vdom 虚拟dom 
 * @returns 
 */
function createDom(vdom) {
    //vDom变成真实dom 和vue相差不大
    let dom = null
    let { type, props } = vdom
    //判断是元素还是文本
    if (type === REACT_TEXT) {
        dom = document.createTextNode(vdom.content)
    } else {
        dom = document.createElement(type)
    }
    //处理属性
    if (props) {
        updateProps(dom, {}, props) //三个参数分别为 真实dom 旧的属性 新的属性 
    }
    //处理子元素
    if (props) {
        let children = props.children
        if (children) {
            if (Array.isArray(children)) {
                for (let child of children) {
                    render(child, dom)
                }
            }
            else {
                render(children, dom)
            }
        }
    }
    return dom
}
/**
 * @name 更新dom属性
 * @param {*} dom 真实dom
 * @param {*} oldProps 旧的props
 * @param {*} newProps 新的props
 */
function updateProps(dom, oldProps, newProps) {
    //处理新属性
    for (let key in newProps) {
        if (key === "children") {
            continue
        } else if (key === "style") {
            let styleObj = newProps[key]
            for (let styleKey in styleObj) {
                dom.style[styleKey] = styleObj[styleKey]
            }
        } else {
            dom[key] = newProps[key]
        }
    }
    //处理就属性
    if (oldProps) {
        //旧的属性在新的中没有就删除
        for (let key in oldProps) {
            if (!newProps[key]) {
                dom[key] = null
            }
        }
    }
}
const ReactDOM = {
    render
}
export default ReactDOM

然后可以进行测试

index.js

import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
import reportWebVitals from './reportWebVitals';
import myReact from "./实现jsx/react"
import myReactDom from "./实现jsx/react-dom"
let element = <h1>5555</h1>
console.log(element); //实际上是一个虚拟dom 通过babel的react.createElement实现
/**
 * 例如  一共三个参数  type是dom节点类型  config配置属性 children子元素 可以接收多个
 */
let element2 = myReact.createElement("h1", {
  className: "test",
  style: {
    color: "red"
  }
}, "55555", myReact.createElement("span", { style: { color: "yellow" } }, 666, 5555))
console.log(element2);
// const root = ReactDOM.createRoot(document.getElementById('root'));
// root.render( //创建组件进行渲染 核心其实是使用了babel将jsx语法转换
//   // <React.StrictMode>
//   //   <App />
//   // </React.StrictMode>
//   element2
// );
myReactDom.render(element2, document.getElementById('root'))

// If you want to start measuring performance in your app, pass a function
// to log results (for example: reportWebVitals(console.log))
// or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
reportWebVitals();

可以看到页面的效果

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值