【 React 】React JSX 转换成真实DOM的过程?

1. 是什么

react通过将组件编写的JSX映射到屏幕,以及组件中的状态发生了变化之后React会将这些「变化」更新到屏幕上

在前面文章了解中,JSX通过babel最终转化成React.createElement这种形式,例如:

javascript 复制代码
<div>
     < img src="avatar.png" className="profile" />
    <Hello />
</div>

会被babel 转换成如下:

javascript 复制代码
React.createElement(
    "div",
    null,
    React.createElement("img", {
        src: "avatar.png",
        className: "profile"
    }),
    React.createElement(Hello, null) 
);

在转化过程中,babel在编译时会判断JSX中组件的首字母:

  • 当首字母为小写时,其被认定为原生DOM标签,createElement的第一个变量被编译为字符串
  • 当首字母为大写时,其被认定为自定义组件,createElement的第一个变量被编译为对象

最终都会通过RenderDOM.render(...)方法进行挂载,如下:

javascript 复制代码
ReactDOM.render(<App />, document.getElementById("root"));

2. 过程

在react中,节点大致可以分成四个类别:

  • 原生标签节点
  • 文本节点
  • 函数组件
  • 类组件

如下所示:

javascript 复制代码
class ClassComponent extends Component {
    static defaultProps = {
        color: "pink"
    };
    render() {
        return ( 
            <div className="border"> 
                <h3>ClassComponent</h3> 
                <p className={this.props.color}>{this.props.name}</p >
            </div> 
        ); 
    }
}
function FunctionComponent(props) {
    return ( 
        <div className="border">
            FunctionComponent
            <p>{props.name}</p >
        </div> 
    ); 
}
const jsx = ( 
    <div className="border">
        <p>xx</p > 
        < a href=" ">xxx</ a> 
        <FunctionComponent name=" " />
        <ClassComponent name=" " color="red" />
    </div> 
);

这些类别最终都会被转化成React.createElement这种形式

React.createElement其被调用时会传入标签类型type,标签属性props及若干子元素children ,作用是生成一个虚拟Dom对象,如下所示:

javascript 复制代码
function createElement(type, config, ...children) {
    if (config) {
    delete config.__self;
    delete config.__source; }
    //源码中做了详细处理,比如过滤掉 ! key ref等
    const props = {
    ...config,
    children: children.map(child =>
    typeof child === "object" ? child : createTextNode(child) ) };
    return {
    type,
    props
    }; 
}
function createTextNode(text) {
    return {
    type: TEXT,
    props: {
    children: [],
    nodeValue: text
    } }; 
}
export default {
    createElement
};

createElement会根据传入的节点信息进行一个判断:

  • 如果是原生标签节点,type是字符串,如div、span
  • 如果是文本节点,type就没有,这里是TEXT
  • 如果是函数组件,type是函数名
  • 如果是类组件,type是类名

虚拟DOM会通过ReactDOM.render进行渲染成真实DOM,使用方法如下:

javascript 复制代码
ReactDOM.render(element,container[,callback])

当首次调用时,容器节点里的所有DOM元素都会被替换,后续的调用则会使用React的diff算法进行高效的更新

如果提供了可选的回调函数callback ,该回调将在组件被渲染或更新之后被执行render大致实

现方法如下:

javascript 复制代码
function render(vnode, container) {
    console.log("vnode", vnode); //虚拟 DOM 对象
    // vnode _> node
    const node = createNode(vnode, container);
    container.appendChild(node); 
}
    // 创建真实DOM节点
function createNode(vnode, parentNode) {
    let node = null;
    const {type, props} = vnode;
    if (type === TEXT) {
        node = document.createTextNode("");
    } else if (typeof type === "string") {
        node = document.createElement(type); 
    } else if (typeof type === "function") {
        node = type.isReactComponent? updateClassComponent(vnode, parentNode) : updateFunctionComponent(vnode, parentNode); 
    } else {
        node = document.createDocumentFragment(); 
    }
    reconcileChildren(props.children, node);
    updateNode(node, props);
    return node;
 }
    //遍历下子 vnode 然后把子vnode-> 真实DOM 再插入到父node中
function reconcileChildren(children, node) {
    for (let i = 0; i < children.length; i++) {
        let child = children[i];
        if (Array.isArray(child)) {
            for (let j = 0; j < child.length; j++) {
                render(child[j], node); 
            } 
        } else {
            render(child, node); 
        } 
    } 
}
function updateNode(node, nextVal) {
    Object.keys(nextVal) .filter(k => k !== "children") .forEach(k => {
        if (k.slice(0, 2) === "on") { 
            node.addEventListener(eventName, nextVal[k]); 
        } else {
                node[k] = nextVal[k]; 
        } 
    }); 
}
//返回真实 dom节点
// 执行函数
function updateFunctionComponent(vnode, parentNode) {
    const {type, props} = vnode;
    let vvnode = type(props);
    const node = createNode(vvnode, parentNode);
    return node; 
}
            // dom
            // 先实例化,再执行render函数
function updateClassComponent(vnode, parentNode) {
    const {type, props} = vnode;
    let cmp = new type(props);
    const vvnode = cmp.render();
    const node = createNode(vvnode, parentNode);
    return node; 
}
export default {
    render
};

3. 总结

渲染流程如下所示:

  • 使用React.createElement或JSX编写React组件,实际上所有的JSX代码最后都会转换成React.createElement(...),Babel帮助我们完成了这个转换的过程。
  • createElement函数对key和ref等特殊的props进行处理,并获取defaultProps对默认props进行赋值,并且对传入的孩子节点进行处理,最终构造成一个虚拟DOM对象
  • ReactDOM.render将生成好的虚拟DOM渲染到指定容器上,其中采用了批处理、事务等机制并且对特定浏览器进行了性能优化,最终转换为真实DOM
相关推荐
崔庆才丨静觅4 小时前
hCaptcha 验证码图像识别 API 对接教程
前端
passerby60615 小时前
完成前端时间处理的另一块版图
前端·github·web components
掘了5 小时前
「2025 年终总结」在所有失去的人中,我最怀念我自己
前端·后端·年终总结
崔庆才丨静觅5 小时前
实用免费的 Short URL 短链接 API 对接说明
前端
崔庆才丨静觅5 小时前
5分钟快速搭建 AI 平台并用它赚钱!
前端
崔庆才丨静觅6 小时前
比官方便宜一半以上!Midjourney API 申请及使用
前端
Moment6 小时前
富文本编辑器在 AI 时代为什么这么受欢迎
前端·javascript·后端
崔庆才丨静觅6 小时前
刷屏全网的“nano-banana”API接入指南!0.1元/张量产高清创意图,开发者必藏
前端
剪刀石头布啊6 小时前
jwt介绍
前端
爱敲代码的小鱼6 小时前
AJAX(异步交互的技术来实现从服务端中获取数据):
前端·javascript·ajax